Passwordlress Flow (WIP)
* API endpoints for embedded login flow * unit tests Signed-off-by: smarcet@gmail.com <smarcet@gmail.com> Change-Id: Ib09f1486f5d9419ee1df64a9d1c41dc7c9a4a65c Depends-on: https://review.opendev.org/c/osf/openstackid/+/791306
This commit is contained in:
parent
75fc12a564
commit
ce9b9a8698
|
@ -0,0 +1,76 @@
|
|||
<?php namespace App\Mail;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class OAuth2PasswordlessOTPMail
|
||||
* @package App\Mail
|
||||
*/
|
||||
class OAuth2PasswordlessOTPMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $otp;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $to;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $lifetime;
|
||||
|
||||
/**
|
||||
* OAuth2PasswordlessOTPMail constructor.
|
||||
* @param string $to
|
||||
* @param string $otp
|
||||
* @param int $lifetime
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
string $to,
|
||||
string $otp,
|
||||
int $lifetime
|
||||
)
|
||||
{
|
||||
$this->to = $to;
|
||||
$this->otp = $otp;
|
||||
$this->lifetime = $lifetime / 60;
|
||||
}
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$subject = sprintf("[%s] Your Verification Code", Config::get('app.app_name'));
|
||||
Log::debug(sprintf("OAuth2PasswordlessOTPMail::build to %s", $this->to));
|
||||
return $this->from(Config::get("mail.from"))
|
||||
->to($this->to)
|
||||
->subject($subject)
|
||||
->view('emails.oauth2_passwordless_otp');
|
||||
}
|
||||
}
|
|
@ -90,6 +90,24 @@ class Client extends BaseEntity implements IClient
|
|||
*/
|
||||
private $pkce_enabled;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="otp_enabled", type="boolean")
|
||||
* @var bool
|
||||
*/
|
||||
private $otp_enabled;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="otp_length", type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $otp_length;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="otp_lifetime", type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $otp_lifetime;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="locked", type="boolean")
|
||||
* @var bool
|
||||
|
@ -343,13 +361,13 @@ class Client extends BaseEntity implements IClient
|
|||
private $admin_users;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Models\OAuth2\RefreshToken", mappedBy="client", cascade={"persist"}, orphanRemoval=true)
|
||||
* @ORM\OneToMany(targetEntity="Models\OAuth2\RefreshToken", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
private $refresh_tokens;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Models\OAuth2\AccessToken", mappedBy="client", cascade={"persist"}, orphanRemoval=true)
|
||||
* @ORM\OneToMany(targetEntity="Models\OAuth2\AccessToken", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
private $access_tokens;
|
||||
|
@ -364,6 +382,12 @@ class Client extends BaseEntity implements IClient
|
|||
*/
|
||||
private $scopes;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Models\OAuth2\OAuth2OTP", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
private $otp_grants;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*/
|
||||
|
@ -374,6 +398,7 @@ class Client extends BaseEntity implements IClient
|
|||
$this->access_tokens = new ArrayCollection();
|
||||
$this->refresh_tokens = new ArrayCollection();
|
||||
$this->admin_users = new ArrayCollection();
|
||||
$this->otp_grants = new ArrayCollection();
|
||||
$this->scopes = new ArrayCollection();
|
||||
$this->locked = false;
|
||||
$this->active = false;
|
||||
|
@ -399,6 +424,7 @@ class Client extends BaseEntity implements IClient
|
|||
$this->max_refresh_token_issuance_basis = 0;
|
||||
$this->max_refresh_token_issuance_qty = 0;
|
||||
$this->pkce_enabled = false;
|
||||
$this->otp_enabled = false;
|
||||
}
|
||||
|
||||
public static $valid_app_types = [
|
||||
|
@ -1603,4 +1629,97 @@ class Client extends BaseEntity implements IClient
|
|||
}
|
||||
$this->pkce_enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPasswordlessEnabled(): bool
|
||||
{
|
||||
return $this->otp_enabled;
|
||||
}
|
||||
|
||||
public function enablePasswordless(): void
|
||||
{
|
||||
$this->otp_enabled = true;
|
||||
$this->token_endpoint_auth_method = OAuth2Protocol::TokenEndpoint_AuthMethod_None;
|
||||
}
|
||||
|
||||
public function disablePasswordless(): void
|
||||
{
|
||||
$this->otp_enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOtpLength(): int
|
||||
{
|
||||
return $this->otp_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $otp_length
|
||||
*/
|
||||
public function setOtpLength(int $otp_length): void
|
||||
{
|
||||
$this->otp_length = $otp_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOtpLifetime(): int
|
||||
{
|
||||
return $this->otp_lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $otp_lifetime
|
||||
*/
|
||||
public function setOtpLifetime(int $otp_lifetime): void
|
||||
{
|
||||
$this->otp_lifetime = $otp_lifetime;
|
||||
}
|
||||
|
||||
public function getOTPGrantsByEmailNotRedeemed(string $email){
|
||||
$criteria = Criteria::create();
|
||||
$criteria->where(Criteria::expr()->eq('email', trim($email)));
|
||||
$criteria->where(Criteria::expr()->isNull("redeemed_at"));
|
||||
return $this->otp_grants->matching($criteria);
|
||||
}
|
||||
|
||||
public function getOTPGrantsByPhoneNumberNotRedeemed(string $phone_number){
|
||||
$criteria = Criteria::create();
|
||||
$criteria->where(Criteria::expr()->eq('phone_number', trim($phone_number)));
|
||||
$criteria->where(Criteria::expr()->isNull("redeemed_at"));
|
||||
return $this->otp_grants->matching($criteria);
|
||||
}
|
||||
|
||||
public function addOTPGrant(OAuth2OTP $otp){
|
||||
if($this->otp_grants->contains($otp)) return;
|
||||
$this->otp_grants->add($otp);
|
||||
$otp->setClient($this);
|
||||
}
|
||||
|
||||
public function removeOTPGrant(OAuth2OTP $otp){
|
||||
if(!$this->otp_grants->contains($otp)) return;
|
||||
$this->otp_grants->removeElement($otp);
|
||||
$otp->clearClient();
|
||||
}
|
||||
|
||||
public function getOTPByValue(string $value):?OAuth2OTP{
|
||||
$criteria = Criteria::create();
|
||||
$criteria->where(Criteria::expr()->eq('value', trim($value)));
|
||||
$res = $this->otp_grants->matching($criteria)->first();
|
||||
return !$res ? null : $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOTP(string $value):bool{
|
||||
return !is_null($this->getOTPByValue($value));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<?php namespace App\Models\OAuth2\Factories;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
|
||||
/**
|
||||
* Class OTPFactory
|
||||
* @package App\Models\OAuth2\Factories
|
||||
*/
|
||||
final class OTPFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @param OAuth2PasswordlessAuthenticationRequest $request
|
||||
* @param IdentifierGenerator $identifier_generator
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
*/
|
||||
static public function buildFromRequest
|
||||
(
|
||||
OAuth2PasswordlessAuthenticationRequest $request,
|
||||
IdentifierGenerator $identifier_generator,
|
||||
?Client $client = null
|
||||
):OAuth2OTP{
|
||||
|
||||
$lifetime = Config::get("otp.lifetime", 120);
|
||||
$length = Config::get("otp.length",6);
|
||||
|
||||
if(!is_null($client)){
|
||||
$lifetime = $client->getOtpLifetime();
|
||||
$length = $client->getOtpLength();
|
||||
}
|
||||
|
||||
$otp = new OAuth2OTP($length, $lifetime);
|
||||
$otp->setConnection($request->getConnection());
|
||||
$otp->setSend($request->getSend());
|
||||
$otp->setLifetime($lifetime);
|
||||
$otp->setNonce($request->getNonce());
|
||||
$otp->setRedirectUrl($request->getRedirectUri());
|
||||
$otp->setScopes($request->getScope());
|
||||
$otp->setEmail($request->getEmail());
|
||||
$otp->setPhoneNumber($request->getPhoneNumber());
|
||||
$identifier_generator->generate($otp);
|
||||
|
||||
if(!is_null($client)){
|
||||
// check that client does not has a value
|
||||
while($client->hasOTP($otp->getValue())){
|
||||
$identifier_generator->generate($otp);
|
||||
}
|
||||
// then add it
|
||||
$client->addOTPGrant($otp);
|
||||
}
|
||||
|
||||
return $otp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $payload
|
||||
* @param IdentifierGenerator $identifier_generator
|
||||
* @return OAuth2OTP
|
||||
*/
|
||||
static public function buildFromPayload(array $payload, IdentifierGenerator $identifier_generator):OAuth2OTP{
|
||||
|
||||
$lifetime = Config::get("otp.lifetime", 120);
|
||||
$length = Config::get("otp.length",6);
|
||||
$otp = new OAuth2OTP($length, $lifetime);
|
||||
$otp->setConnection($payload[OAuth2Protocol::OAuth2PasswordlessConnection]);
|
||||
$otp->setSend($payload[OAuth2Protocol::OAuth2PasswordlessSend]);
|
||||
$otp->setScopes($payload[OAuth2Protocol::OAuth2Protocol_Scope]);
|
||||
$otp->setLifetime($lifetime);
|
||||
$otp->setNonce($payload[OAuth2Protocol::OAuth2Protocol_Nonce] ?? null);
|
||||
$otp->setRedirectUrl($payload[OAuth2Protocol::OAuth2Protocol_RedirectUri] ?? null);
|
||||
$otp->setEmail($payload[OAuth2Protocol::OAuth2PasswordlessEmail] ?? null);
|
||||
$otp->setPhoneNumber($payload[OAuth2Protocol::OAuth2PasswordlessPhoneNumber] ?? null);
|
||||
$identifier_generator->generate($otp);
|
||||
|
||||
return $otp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
<?php namespace Models\OAuth2;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Models\Utils\BaseEntity;
|
||||
use Doctrine\ORM\Mapping AS ORM;
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use models\exceptions\ValidationException;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use OAuth2\Requests\OAuth2AccessTokenRequestPasswordless;
|
||||
use Utils\IPHelper;
|
||||
use Utils\Model\Identifier;
|
||||
use Zend\Math\Rand;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="App\Repositories\DoctrineOAuth2OTPRepository")
|
||||
* @ORM\Table(name="oauth2_otp")
|
||||
* Class OTP
|
||||
* @package Models\OAuth2
|
||||
*/
|
||||
class OAuth2OTP extends BaseEntity implements Identifier
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="value", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="length", type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $length;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="`connection`", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="send", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $send;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="scopes", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $scopes;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="email", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $email;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="phone_number", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $phone_number;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="nonce", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $nonce;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="lifetime", type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $lifetime;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="redirect_url", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $redirect_url;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @ORM\Column(name="redeemed_at", type="datetime")
|
||||
*/
|
||||
private $redeemed_at;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(name="redeemed_from_ip", type="string")
|
||||
*/
|
||||
private $redeemed_from_ip;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="redeemed_attempts", type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $redeemed_attempts;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Models\OAuth2\Client", inversedBy="otp_grants", cascade={"persist"})
|
||||
* @ORM\JoinColumn(name="oauth2_client_id", referencedColumnName="id", nullable=true)
|
||||
* @var Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* OAuth2OTP constructor.
|
||||
* @param int $length
|
||||
* @param int $lifetime
|
||||
*/
|
||||
public function __construct(int $length, int $lifetime = 0 )
|
||||
{
|
||||
parent::__construct();
|
||||
$this->length = $length;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->redeemed_at = null;
|
||||
$this->redeemed_attempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getConnection(): string
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connection
|
||||
*/
|
||||
public function setConnection(string $connection): void
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSend(): string
|
||||
{
|
||||
return $this->send;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $send
|
||||
*/
|
||||
public function setSend(string $send): void
|
||||
{
|
||||
$this->send = $send;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getScopes(): string
|
||||
{
|
||||
return $this->scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scopes
|
||||
*/
|
||||
public function setScopes(string $scopes): void
|
||||
{
|
||||
$this->scopes = $scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
*/
|
||||
public function setEmail(?string $email): void
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPhoneNumber(): ?string
|
||||
{
|
||||
return $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $phone_number
|
||||
*/
|
||||
public function setPhoneNumber(?string $phone_number): void
|
||||
{
|
||||
$this->phone_number = $phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonce(): ?string
|
||||
{
|
||||
return $this->nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nonce
|
||||
*/
|
||||
public function setNonce(?string $nonce): void
|
||||
{
|
||||
$this->nonce = $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRedirectUrl(): ?string
|
||||
{
|
||||
return $this->redirect_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $redirect_url
|
||||
*/
|
||||
public function setRedirectUrl(?string $redirect_url): void
|
||||
{
|
||||
$this->redirect_url = $redirect_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getRedeemedAt(): ?\DateTime
|
||||
{
|
||||
return $this->redeemed_at;
|
||||
}
|
||||
|
||||
public function isRedeemed():bool{
|
||||
return !is_null($this->redeemed_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function redeem(): void
|
||||
{
|
||||
if(!is_null($this->redeemed_at))
|
||||
throw new ValidationException("OTP is already redeemed.");
|
||||
$this->redeemed_at = new \DateTime('now', new \DateTimeZone('UTC'));
|
||||
$this->redeemed_from_ip = IPHelper::getUserIp();
|
||||
|
||||
Log::debug(sprintf("OAuth2OTP::redeem from ip %s", $this->redeemed_from_ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Client
|
||||
*/
|
||||
public function getClient(): Client
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function hasClient():bool{
|
||||
return !is_null($this->client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
*/
|
||||
public function setClient(Client $client): void
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLifetime(): int
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lifetime
|
||||
*/
|
||||
public function setLifetime(int $lifetime): void
|
||||
{
|
||||
$this->lifetime = $lifetime;
|
||||
}
|
||||
|
||||
public function getRemainingLifetime()
|
||||
{
|
||||
//check is refresh token is stills alive... (ZERO is infinite lifetime)
|
||||
if (intval($this->lifetime) == 0) {
|
||||
return 0;
|
||||
}
|
||||
$created_at = clone $this->created_at;
|
||||
$created_at->add(new DateInterval('PT' . intval($this->lifetime) . 'S'));
|
||||
$now = new DateTime(gmdate("Y-m-d H:i:s", time()), new DateTimeZone("UTC"));
|
||||
//check validity...
|
||||
if ($now > $created_at) {
|
||||
return -1;
|
||||
}
|
||||
$seconds = abs($created_at->getTimestamp() - $now->getTimestamp());;
|
||||
|
||||
return $seconds;
|
||||
}
|
||||
|
||||
public function isAlive():bool{
|
||||
return $this->getRemainingLifetime() >= 0;
|
||||
}
|
||||
|
||||
public function clearClient():void{
|
||||
$this->client = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
const MaxRedeemAttempts = 3;
|
||||
|
||||
public function logRedeemAttempt():void{
|
||||
if($this->redeemed_attempts < self::MaxRedeemAttempts){
|
||||
$this->redeemed_attempts = $this->redeemed_attempts + 1;
|
||||
Log::debug(sprintf("OAuth2OTP::logRedeemAttempt redeemed_attempts %s", $this->redeemed_attempts));
|
||||
}
|
||||
}
|
||||
|
||||
public function isValid():bool{
|
||||
return ($this->redeemed_attempts < self::MaxRedeemAttempts) && $this->isAlive();
|
||||
}
|
||||
|
||||
public function getUserName():?string{
|
||||
return $this->connection == OAuth2Protocol::OAuth2PasswordlessEmail ? $this->email : $this->phone_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scope
|
||||
* @return bool
|
||||
*/
|
||||
public function allowScope(string $scope):bool{
|
||||
$s1 = explode(" ", $scope);
|
||||
$s2 = explode(" ", $this->scopes);
|
||||
return count(array_diff($s1, $s2)) == 0;
|
||||
}
|
||||
|
||||
public function setValue(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return "otp";
|
||||
}
|
||||
|
||||
const VsChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
public function generateValue(): string
|
||||
{
|
||||
// calculate value
|
||||
// entropy(SHANNON FANO Approx) len * log(count(VsChar))/log(2) = bits of entropy
|
||||
$this->value = Rand::getString($this->length, self::VsChar);
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2AccessTokenRequestPasswordless $request
|
||||
* @param int $length
|
||||
* @return OAuth2OTP
|
||||
*/
|
||||
public static function fromRequest(OAuth2AccessTokenRequestPasswordless $request, int $length):OAuth2OTP{
|
||||
$instance = new self($length);
|
||||
$instance->connection = $request->getConnection();
|
||||
$instance->email = $request->getEmail();
|
||||
$instance->phone_number = $request->getPhoneNumber();
|
||||
$instance->scopes = $request->getScopes();
|
||||
$instance->value = $request->getOTP();
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// non db fields
|
||||
|
||||
private $auth_time;
|
||||
|
||||
private $user_id;
|
||||
|
||||
/**
|
||||
* @param int $auth_time
|
||||
*/
|
||||
public function setAuthTime(int $auth_time): void
|
||||
{
|
||||
$this->auth_time = $auth_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $user_id
|
||||
*/
|
||||
public function setUserId($user_id): void
|
||||
{
|
||||
$this->user_id = $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuthTime()
|
||||
{
|
||||
return $this->auth_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->user_id;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -12,7 +12,6 @@
|
|||
* limitations under the License.
|
||||
**/
|
||||
use App\Models\Utils\BaseEntity;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping AS ORM;
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="App\Repositories\DoctrineOAuth2TrailExceptionRepository")
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<?php namespace App\Repositories;
|
||||
/**
|
||||
* Copyright 2019 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
/**
|
||||
* Class DoctrineOAuth2OTPRepository
|
||||
* @package App\Repositories
|
||||
*/
|
||||
class DoctrineOAuth2OTPRepository
|
||||
extends ModelDoctrineRepository
|
||||
implements IOAuth2OTPRepository
|
||||
{
|
||||
|
||||
protected function getBaseEntity()
|
||||
{
|
||||
return OAuth2OTP::class;
|
||||
}
|
||||
|
||||
public function getByValue(string $value): ?OAuth2OTP
|
||||
{
|
||||
return $this->findOneBy(['value' => trim($value)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connection
|
||||
* @param string $user_name
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP|null
|
||||
*/
|
||||
public function getByConnectionAndUserNameNotRedeemed
|
||||
(
|
||||
string $connection,
|
||||
string $user_name,
|
||||
?Client $client
|
||||
):?OAuth2OTP
|
||||
{
|
||||
$query = $this->getEntityManager()
|
||||
->createQueryBuilder()
|
||||
->select("e")
|
||||
->from($this->getBaseEntity(), "e")
|
||||
->where("e.connection = (:connection)")
|
||||
->andWhere("(e.email = (:user_name) or e.phone_number = (:user_name))")
|
||||
->andWhere("e.redeemed_at is null")
|
||||
->setParameter("connection", $connection)
|
||||
->setParameter("user_name", $user_name);
|
||||
// add client id condition
|
||||
if(!is_null($client)){
|
||||
$query->join("e.client", "c")->andWhere("c.id = :client_id")
|
||||
->setParameter("client_id", $client->getId());
|
||||
}
|
||||
$query->addOrderBy("e.id", "DESC");
|
||||
return $query->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\Auth\Models\SpamEstimatorFeed;
|
||||
use App\libs\Auth\Models\UserRegistrationRequest;
|
||||
use App\libs\Auth\Repositories\IBannedIPRepository;
|
||||
|
@ -20,6 +21,7 @@ use App\libs\Auth\Repositories\IUserExceptionTrailRepository;
|
|||
use App\libs\Auth\Repositories\IUserPasswordResetRequestRepository;
|
||||
use App\libs\Auth\Repositories\IUserRegistrationRequestRepository;
|
||||
use App\libs\Auth\Repositories\IWhiteListedIPRepository;
|
||||
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||
use App\libs\OAuth2\Repositories\IOAuth2TrailExceptionRepository;
|
||||
use App\Models\Repositories\IDisqusSSOProfileRepository;
|
||||
use App\Models\Repositories\IRocketChatSSOProfileRepository;
|
||||
|
@ -43,6 +45,7 @@ use Models\OAuth2\ApiScope;
|
|||
use Models\OAuth2\ApiScopeGroup;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\ClientPublicKey;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use Models\OAuth2\OAuth2TrailException;
|
||||
use Models\OAuth2\RefreshToken;
|
||||
use Models\OAuth2\ResourceServer;
|
||||
|
@ -67,43 +70,45 @@ use OAuth2\Repositories\IServerPrivateKeyRepository;
|
|||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use OpenId\Repositories\IOpenIdAssociationRepository;
|
||||
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
||||
|
||||
/**
|
||||
* Class RepositoriesProvider
|
||||
* @package Repositories
|
||||
*/
|
||||
final class RepositoriesProvider extends ServiceProvider implements DeferrableProvider
|
||||
{
|
||||
public function boot(){
|
||||
public function boot()
|
||||
{
|
||||
}
|
||||
|
||||
public function register(){
|
||||
public function register()
|
||||
{
|
||||
|
||||
App::singleton(IGroupRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(Group::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IUserPasswordResetRequestRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(UserPasswordResetRequest::class);
|
||||
})
|
||||
;
|
||||
});
|
||||
|
||||
App::singleton(IServerExtensionRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ServerExtension::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IOpenIdTrustedSiteRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(OpenIdTrustedSite::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IOpenIdAssociationRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(OpenIdAssociation::class);
|
||||
}
|
||||
);
|
||||
|
@ -111,171 +116,184 @@ final class RepositoriesProvider extends ServiceProvider implements DeferrablePr
|
|||
// doctrine repos
|
||||
|
||||
App::singleton(IServerConfigurationRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ServerConfiguration::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IUserExceptionTrailRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(UserExceptionTrail::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IBannedIPRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(BannedIP::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(IWhiteListedIPRepository::class, function (){
|
||||
App::singleton(IWhiteListedIPRepository::class, function () {
|
||||
return EntityManager::getRepository(WhiteListedIP::class);
|
||||
});
|
||||
|
||||
App::singleton(IUserRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(User::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IResourceServerRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ResourceServer::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IApiRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(Api::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IApiEndpointRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ApiEndpoint::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IClientRepository::class,
|
||||
function(){
|
||||
IClientRepository::class,
|
||||
function () {
|
||||
return EntityManager::getRepository(Client::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IAccessTokenRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(AccessToken::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IRefreshTokenRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(RefreshToken::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IApiScopeRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ApiScope::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IApiScopeGroupRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ApiScopeGroup::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IOAuth2TrailExceptionRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(OAuth2TrailException::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IClientPublicKeyRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ClientPublicKey::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IServerPrivateKeyRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(ServerPrivateKey::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IUserRegistrationRequestRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(UserRegistrationRequest::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
ISpamEstimatorFeedRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(SpamEstimatorFeed::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IDisqusSSOProfileRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(DisqusSSOProfile::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IRocketChatSSOProfileRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(RocketChatSSOProfile::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IStreamChatSSOProfileRepository::class,
|
||||
function(){
|
||||
function () {
|
||||
return EntityManager::getRepository(StreamChatSSOProfile::class);
|
||||
}
|
||||
);
|
||||
|
||||
App::singleton(
|
||||
IOAuth2OTPRepository::class,
|
||||
function () {
|
||||
return EntityManager::getRepository(OAuth2OTP::class);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function provides()
|
||||
{
|
||||
return [
|
||||
IServerConfigurationRepository::class,
|
||||
IGroupRepository::class,
|
||||
IOpenIdAssociationRepository::class,
|
||||
IUserPasswordResetRequestRepository::class,
|
||||
IServerExtensionRepository::class,
|
||||
IOpenIdTrustedSiteRepository::class,
|
||||
IOpenIdAssociationRepository::class,
|
||||
IServerConfigurationRepository::class,
|
||||
IUserExceptionTrailRepository::class,
|
||||
IBannedIPRepository::class,
|
||||
IWhiteListedIPRepository::class,
|
||||
IUserRepository::class,
|
||||
IResourceServerRepository::class,
|
||||
IApiRepository::class,
|
||||
IApiEndpointRepository::class,
|
||||
IClientRepository::class,
|
||||
IAccessTokenRepository::class,
|
||||
IRefreshTokenRepository::class,
|
||||
IApiScopeRepository::class,
|
||||
IApiScopeGroupRepository::class,
|
||||
IOAuth2TrailExceptionRepository::class,
|
||||
IClientPublicKeyRepository::class,
|
||||
IServerPrivateKeyRepository::class,
|
||||
IClientRepository::class,
|
||||
IApiScopeGroupRepository::class,
|
||||
IApiEndpointRepository::class,
|
||||
IRefreshTokenRepository::class,
|
||||
IAccessTokenRepository::class,
|
||||
IApiScopeRepository::class,
|
||||
IApiRepository::class,
|
||||
IResourceServerRepository::class,
|
||||
IWhiteListedIPRepository::class,
|
||||
IUserRegistrationRequestRepository::class,
|
||||
ISpamEstimatorFeedRepository::class,
|
||||
IDisqusSSOProfileRepository::class,
|
||||
IRocketChatSSOProfileRepository::class,
|
||||
IStreamChatSSOProfileRepository::class,
|
||||
IOAuth2OTPRepository::class,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -128,6 +128,7 @@ final class UserService extends AbstractService implements IUserService
|
|||
if(count($default_groups) > 0){
|
||||
$payload['groups'] = $default_groups;
|
||||
}
|
||||
|
||||
$user = UserFactory::build($payload);
|
||||
|
||||
$this->user_repository->add($user);
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
**/
|
||||
|
||||
use App\Http\Utils\IUserIPHelperProvider;
|
||||
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||
use App\Services\Auth\IUserService;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use OAuth2\Services\AccessTokenGenerator;
|
||||
use OAuth2\Services\AuthorizationCodeGenerator;
|
||||
use OAuth2\Services\IApiScopeService;
|
||||
use OAuth2\Services\OAuth2ServiceCatalog;
|
||||
use OAuth2\Services\RefreshTokenGenerator;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
use Illuminate\Support\Facades\App;
|
||||
/**
|
||||
|
@ -70,9 +70,7 @@ final class OAuth2ServiceProvider extends ServiceProvider implements DeferrableP
|
|||
App::make(UtilsServiceCatalog::CacheService),
|
||||
App::make(UtilsServiceCatalog::AuthenticationService),
|
||||
App::make(OAuth2ServiceCatalog::UserConsentService),
|
||||
new AuthorizationCodeGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
||||
new AccessTokenGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
||||
new RefreshTokenGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
||||
App::make(IdentifierGenerator::class),
|
||||
App::make(\OAuth2\Repositories\IServerPrivateKeyRepository::class),
|
||||
new HttpIClientJWKSetReader,
|
||||
App::make(OAuth2ServiceCatalog::SecurityContextService),
|
||||
|
@ -82,9 +80,11 @@ final class OAuth2ServiceProvider extends ServiceProvider implements DeferrableP
|
|||
App::make(\OAuth2\Repositories\IAccessTokenRepository::class),
|
||||
App::make(\OAuth2\Repositories\IRefreshTokenRepository::class),
|
||||
App::make(\OAuth2\Repositories\IResourceServerRepository::class),
|
||||
App::make(IOAuth2OTPRepository::class),
|
||||
App::make(IUserIPHelperProvider::class),
|
||||
App::make(IApiScopeService::class),
|
||||
App::make(UtilsServiceCatalog::TransactionService)
|
||||
App::make(IUserService::class),
|
||||
App::make(UtilsServiceCatalog::TransactionService),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -95,6 +95,7 @@ final class OAuth2ServiceProvider extends ServiceProvider implements DeferrableP
|
|||
public function provides()
|
||||
{
|
||||
return [
|
||||
IdentifierGenerator::class,
|
||||
\OAuth2\IResourceServerContext::class,
|
||||
OAuth2ServiceCatalog::ClientCredentialGenerator,
|
||||
OAuth2ServiceCatalog::ClientService,
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
|
||||
use App\Http\Utils\IUserIPHelperProvider;
|
||||
use App\libs\Auth\Models\IGroupSlugs;
|
||||
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||
use App\Models\OAuth2\Factories\OTPFactory;
|
||||
use App\Services\AbstractService;
|
||||
use App\Services\Auth\IUserService;
|
||||
use App\Strategies\OTP\OTPChannelStrategyFactory;
|
||||
use App\Strategies\OTP\OTPTypeBuilderStrategyFactory;
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -23,6 +28,9 @@ use jwt\IBasicJWT;
|
|||
use jwt\impl\JWTClaimSet;
|
||||
use jwt\JWTClaim;
|
||||
use models\exceptions\ValidationException;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Exceptions\InvalidOTPException;
|
||||
use OAuth2\Models\AccessToken;
|
||||
use Models\OAuth2\AccessToken as AccessTokenDB;
|
||||
use Models\OAuth2\RefreshToken as RefreshTokenDB;
|
||||
|
@ -48,6 +56,7 @@ use OAuth2\Repositories\IRefreshTokenRepository;
|
|||
use OAuth2\Repositories\IResourceServerRepository;
|
||||
use OAuth2\Requests\OAuth2AuthenticationRequest;
|
||||
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
||||
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||
use OAuth2\Services\IApiScopeService;
|
||||
use OAuth2\Services\ITokenService;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
|
@ -66,7 +75,7 @@ use Utils\Exceptions\UnacquiredLockException;
|
|||
use utils\json_types\JsonValue;
|
||||
use utils\json_types\NumericDate;
|
||||
use utils\json_types\StringOrURI;
|
||||
use Utils\Model\Identifier;
|
||||
use Utils\Model\AbstractIdentifier;
|
||||
use Utils\Services\IAuthService;
|
||||
use Utils\Services\ICacheService;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
|
@ -100,6 +109,10 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
* @var IClientService
|
||||
*/
|
||||
private $client_service;
|
||||
/**
|
||||
* @var IUserService
|
||||
*/
|
||||
private $user_service;
|
||||
/**
|
||||
* @var ILockManagerService
|
||||
*/
|
||||
|
@ -123,15 +136,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
/**
|
||||
* @var IdentifierGenerator
|
||||
*/
|
||||
private $auth_code_generator;
|
||||
/**
|
||||
* @var IdentifierGenerator
|
||||
*/
|
||||
private $access_token_generator;
|
||||
/**
|
||||
* @var IdentifierGenerator
|
||||
*/
|
||||
private $refresh_token_generator;
|
||||
private $identifier_generator;
|
||||
|
||||
/**
|
||||
* @var IServerPrivateKeyRepository
|
||||
|
@ -187,6 +192,35 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
*/
|
||||
private $ip_helper;
|
||||
|
||||
/**
|
||||
* @var IOAuth2OTPRepository
|
||||
*/
|
||||
private $otp_repository;
|
||||
|
||||
/**
|
||||
* TokenService constructor.
|
||||
* @param IClientService $client_service
|
||||
* @param ILockManagerService $lock_manager_service
|
||||
* @param IServerConfigurationService $configuration_service
|
||||
* @param ICacheService $cache_service
|
||||
* @param IAuthService $auth_service
|
||||
* @param IUserConsentService $user_consent_service
|
||||
* @param IdentifierGenerator $identifier_generator
|
||||
* @param IServerPrivateKeyRepository $server_private_key_repository
|
||||
* @param IClientJWKSetReader $jwk_set_reader_service
|
||||
* @param ISecurityContextService $security_context_service
|
||||
* @param IPrincipalService $principal_service
|
||||
* @param IdTokenBuilder $id_token_builder
|
||||
* @param IClientRepository $client_repository
|
||||
* @param IAccessTokenRepository $access_token_repository
|
||||
* @param IRefreshTokenRepository $refresh_token_repository
|
||||
* @param IResourceServerRepository $resource_server_repository
|
||||
* @param IOAuth2OTPRepository $otp_repository
|
||||
* @param IUserIPHelperProvider $ip_helper
|
||||
* @param IApiScopeService $scope_service
|
||||
* @param IUserService $user_service
|
||||
* @param ITransactionService $tx_service
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
IClientService $client_service,
|
||||
|
@ -195,9 +229,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
ICacheService $cache_service,
|
||||
IAuthService $auth_service,
|
||||
IUserConsentService $user_consent_service,
|
||||
IdentifierGenerator $auth_code_generator,
|
||||
IdentifierGenerator $access_token_generator,
|
||||
IdentifierGenerator $refresh_token_generator,
|
||||
IdentifierGenerator $identifier_generator,
|
||||
IServerPrivateKeyRepository $server_private_key_repository,
|
||||
IClientJWKSetReader $jwk_set_reader_service,
|
||||
ISecurityContextService $security_context_service,
|
||||
|
@ -207,8 +239,10 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
IAccessTokenRepository $access_token_repository,
|
||||
IRefreshTokenRepository $refresh_token_repository,
|
||||
IResourceServerRepository $resource_server_repository,
|
||||
IOAuth2OTPRepository $otp_repository,
|
||||
IUserIPHelperProvider $ip_helper,
|
||||
IApiScopeService $scope_service,
|
||||
IUserService $user_service,
|
||||
ITransactionService $tx_service
|
||||
)
|
||||
{
|
||||
|
@ -220,9 +254,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
$this->cache_service = $cache_service;
|
||||
$this->auth_service = $auth_service;
|
||||
$this->user_consent_service = $user_consent_service;
|
||||
$this->auth_code_generator = $auth_code_generator;
|
||||
$this->access_token_generator = $access_token_generator;
|
||||
$this->refresh_token_generator = $refresh_token_generator;
|
||||
$this->identifier_generator = $identifier_generator;
|
||||
$this->server_private_key_repository = $server_private_key_repository;
|
||||
$this->jwk_set_reader_service = $jwk_set_reader_service;
|
||||
$this->security_context_service = $security_context_service;
|
||||
|
@ -234,6 +266,8 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
$this->resource_server_repository = $resource_server_repository;
|
||||
$this->ip_helper = $ip_helper;
|
||||
$this->scope_service = $scope_service;
|
||||
$this->user_service = $user_service;
|
||||
$this->otp_repository = $otp_repository;
|
||||
|
||||
Event::listen('oauth2.client.delete', function ($client_id) {
|
||||
$this->revokeClientRelatedTokens($client_id);
|
||||
|
@ -248,13 +282,13 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
* Creates a brand new authorization code
|
||||
* @param OAuth2AuthorizationRequest $request
|
||||
* @param bool $has_previous_user_consent
|
||||
* @return Identifier
|
||||
* @return AbstractIdentifier
|
||||
*/
|
||||
public function createAuthorizationCode
|
||||
(
|
||||
OAuth2AuthorizationRequest $request,
|
||||
bool $has_previous_user_consent = false
|
||||
): Identifier
|
||||
): AbstractIdentifier
|
||||
{
|
||||
|
||||
$user = $this->auth_service->getCurrentUser();
|
||||
|
@ -276,7 +310,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
$prompt = $request->getPrompt(true);
|
||||
}
|
||||
|
||||
$code = $this->auth_code_generator->generate
|
||||
$code = $this->identifier_generator->generate
|
||||
(
|
||||
AuthorizationCode::create
|
||||
(
|
||||
|
@ -354,7 +388,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
public function createAccessToken(AuthorizationCode $auth_code, $redirect_uri = null)
|
||||
{
|
||||
|
||||
$access_token = $this->access_token_generator->generate
|
||||
$access_token = $this->identifier_generator->generate
|
||||
(
|
||||
AccessToken::create
|
||||
(
|
||||
|
@ -462,7 +496,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
public function createAccessTokenFromParams($client_id, $scope, $audience, $user_id = null)
|
||||
{
|
||||
|
||||
$access_token = $this->access_token_generator->generate(AccessToken::createFromParams
|
||||
$access_token = $this->identifier_generator->generate(AccessToken::createFromParams
|
||||
(
|
||||
$scope,
|
||||
$client_id,
|
||||
|
@ -559,7 +593,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
}
|
||||
|
||||
//create new access token
|
||||
$access_token = $this->access_token_generator->generate
|
||||
$access_token = $this->identifier_generator->generate
|
||||
(
|
||||
AccessToken::createFromRefreshToken
|
||||
(
|
||||
|
@ -830,7 +864,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
*/
|
||||
public function createRefreshToken(AccessToken &$access_token, $refresh_cache = false)
|
||||
{
|
||||
$refresh_token = $this->refresh_token_generator->generate(
|
||||
$refresh_token = $this->identifier_generator->generate(
|
||||
RefreshToken::create(
|
||||
$access_token,
|
||||
$this->configuration_service->getConfigValue('OAuth2.RefreshToken.Lifetime')
|
||||
|
@ -1485,4 +1519,157 @@ final class TokenService extends AbstractService implements ITokenService
|
|||
return $this->getAccessToken($db_access_token->getValue(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2PasswordlessAuthenticationRequest $request
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createOTPFromRequest(OAuth2PasswordlessAuthenticationRequest $request, ?Client $client):OAuth2OTP{
|
||||
return $this->tx_service->transaction(function() use($request, $client){
|
||||
|
||||
$otp = OTPFactory::buildFromRequest($request, $this->identifier_generator, $client);
|
||||
|
||||
if(!is_null($client)){
|
||||
// invalidate not redeemed former ones
|
||||
$codes = $otp->getConnection() == OAuth2Protocol::OAuth2PasswordlessConnectionEmail ?
|
||||
$client->getOTPGrantsByEmailNotRedeemed($otp->getUserName()):
|
||||
$client->getOTPGrantsByPhoneNumberNotRedeemed($otp->getUserName());
|
||||
foreach ($codes as $code){
|
||||
if($code->getValue() == $otp->getValue()) continue;
|
||||
$client->removeOTPGrant($code);
|
||||
}
|
||||
}
|
||||
// create channel and value to send ( depending on connection and send params )
|
||||
OTPChannelStrategyFactory::build($otp->getConnection())->send
|
||||
(
|
||||
OTPTypeBuilderStrategyFactory::build($otp->getSend()),
|
||||
$otp
|
||||
);
|
||||
return $otp;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otp
|
||||
* @param Client|null $client
|
||||
* @return AccessToken
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createAccessTokenFromOTP(OAuth2OTP $otp, ?Client $client): AccessToken
|
||||
{
|
||||
// build current audience ...
|
||||
$audience = $this->scope_service->getStrAudienceByScopeNames
|
||||
(
|
||||
explode
|
||||
(
|
||||
OAuth2Protocol::OAuth2Protocol_Scope_Delimiter,
|
||||
$otp->getScopes()
|
||||
)
|
||||
);
|
||||
|
||||
$access_token = $this->identifier_generator->generate
|
||||
(
|
||||
AccessToken::createFromOTP
|
||||
(
|
||||
$otp,
|
||||
! is_null($client) ? $client->getClientId() : null,
|
||||
$audience,
|
||||
$this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime')
|
||||
)
|
||||
);
|
||||
|
||||
$db_otp = $this->tx_service->transaction(function() use($otp, $client){
|
||||
// find first db OTP by connection , by username (email/phone) number and client not redeemed
|
||||
$db_otp = $this->otp_repository->getByConnectionAndUserNameNotRedeemed
|
||||
(
|
||||
$otp->getConnection(),
|
||||
$otp->getUserName(),
|
||||
$client
|
||||
);
|
||||
if(is_null($db_otp)){
|
||||
// otp no emitted
|
||||
throw new InvalidOTPException("Non existent OTP.");
|
||||
}
|
||||
$db_otp->logRedeemAttempt();
|
||||
return $db_otp;
|
||||
});
|
||||
|
||||
return $this->tx_service->transaction(function() use($otp, $db_otp, $client, $access_token){
|
||||
|
||||
if( $db_otp->getValue() != $otp->getValue() ||
|
||||
$db_otp->getConnection() != $otp->getConnection() ||
|
||||
!$db_otp->isValid() ||
|
||||
($db_otp->hasClient() && is_null($client)) ||
|
||||
($db_otp->hasClient() && !is_null($client) && $client->getClientId() != $db_otp->getClient()->getClientId()) ||
|
||||
($db_otp->getUserName() != $otp->getUserName()) ||
|
||||
(!$db_otp->allowScope($otp->getScopes()))
|
||||
){
|
||||
if(!$db_otp->isValid() || !$db_otp->allowScope($otp->getScopes()))
|
||||
throw new InvalidOTPException("Invalidated OTP.");
|
||||
|
||||
throw new InvalidOTPException("Non existent OTP.");
|
||||
}
|
||||
// we have a valid OTP
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($otp->getUserName());
|
||||
if(is_null($user))// we need to create a new one ( auto register)
|
||||
{
|
||||
Log::debug(sprintf("TokenService::createAccessTokenFromOTP user %s does not exists ...", $otp->getUserName()));
|
||||
|
||||
$user = $this->user_service->registerUser([
|
||||
'email' => $otp->getUserName(),
|
||||
'email_verified' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$otp->setAuthTime(time());
|
||||
$otp->setUserId($user->getId());
|
||||
$otp->setNonce($db_otp->getNonce());
|
||||
|
||||
$db_otp->redeem();
|
||||
|
||||
// TODO; move to a factory
|
||||
$value = $access_token->getValue();
|
||||
$hashed_value = Hash::compute('sha256', $value);
|
||||
|
||||
$access_token_db = new AccessTokenDB();
|
||||
$access_token_db->setValue($hashed_value);
|
||||
$access_token_db->setFromIp($this->ip_helper->getCurrentUserIpAddress());
|
||||
$access_token_db->setLifetime($access_token->getLifetime());
|
||||
$access_token_db->setScope($access_token->getScope());
|
||||
$access_token_db->setAudience($access_token->getAudience());
|
||||
$access_token_db->setClient($client);
|
||||
$access_token_db->setOwner($user);
|
||||
|
||||
$this->access_token_repository->add($access_token_db);
|
||||
|
||||
//check if use refresh tokens...
|
||||
|
||||
if
|
||||
(
|
||||
$client->useRefreshToken() &&
|
||||
$client->isPasswordlessEnabled() &&
|
||||
str_contains($otp->getScopes(), OAuth2Protocol::OfflineAccess_Scope)
|
||||
) {
|
||||
Log::debug('TokenService::createAccessTokenFromOTP creating refresh token ...');
|
||||
$this->createRefreshToken($access_token);
|
||||
}
|
||||
|
||||
$this->storesAccessTokenOnCache($access_token);
|
||||
// stores brand new access token hash value on a set by client id...
|
||||
{
|
||||
if (!is_null($client))
|
||||
$this->cache_service->addMemberSet($client->getClientId() . TokenService::ClientAccessTokenPrefixList, $hashed_value);
|
||||
|
||||
$this->cache_service->incCounter
|
||||
(
|
||||
$client->getClientId() . TokenService::ClientAccessTokensQty,
|
||||
TokenService::ClientAccessTokensQtyLifetime
|
||||
);
|
||||
}
|
||||
|
||||
return $access_token;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -15,8 +15,8 @@
|
|||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use OpenId\Services\NonceUniqueIdentifierGenerator;
|
||||
use OpenId\Services\OpenIdServiceCatalog;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
/**
|
||||
* Class OpenIdProvider
|
||||
|
@ -44,7 +44,7 @@ final class OpenIdProvider extends ServiceProvider implements DeferrableProvider
|
|||
App::make(UtilsServiceCatalog::LockManagerService),
|
||||
App::make(UtilsServiceCatalog::CacheService),
|
||||
App::make(UtilsServiceCatalog::ServerConfigurationService),
|
||||
new NonceUniqueIdentifierGenerator(App::make(UtilsServiceCatalog::CacheService))
|
||||
App::make(IdentifierGenerator::class),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ use App\Models\Utils\BaseEntity;
|
|||
use App\Repositories\IServerConfigurationRepository;
|
||||
use App\Services\Utils\DoctrineTransactionService;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
use Utils\Services\UniqueIdentifierGenerator;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
@ -31,6 +33,8 @@ final class UtilsProvider extends ServiceProvider implements DeferrableProvider
|
|||
*/
|
||||
public function register()
|
||||
{
|
||||
App::singleton(IdentifierGenerator::class, UniqueIdentifierGenerator::class);
|
||||
|
||||
App::singleton(UtilsServiceCatalog::CacheService, RedisCacheService::class);
|
||||
App::singleton(UtilsServiceCatalog::TransactionService, function(){
|
||||
return new DoctrineTransactionService(BaseEntity::EntityManager);
|
||||
|
@ -55,12 +59,14 @@ final class UtilsProvider extends ServiceProvider implements DeferrableProvider
|
|||
return new ExternalUrlService();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function provides()
|
||||
{
|
||||
return
|
||||
[
|
||||
IdentifierGenerator::class,
|
||||
UtilsServiceCatalog::CacheService,
|
||||
UtilsServiceCatalog::TransactionService,
|
||||
UtilsServiceCatalog::LogService,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
/**
|
||||
* Interface IOTPChannelStrategy
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
interface IOTPChannelStrategy
|
||||
{
|
||||
/**
|
||||
* @param IOTPTypeBuilderStrategy $typeBuilderStrategy
|
||||
* @param OAuth2OTP $otp
|
||||
* @return bool
|
||||
*/
|
||||
public function send(IOTPTypeBuilderStrategy $typeBuilderStrategy, OAuth2OTP $otp):bool;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
/**
|
||||
* Interface IOTPTypeBuilderStrategy
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
interface IOTPTypeBuilderStrategy
|
||||
{
|
||||
public function generate(OAuth2OTP $otp):string;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\Mail\OAuth2PasswordlessOTPMail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
/**
|
||||
* Class OTPChannelEmailStrategy
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
final class OTPChannelEmailStrategy
|
||||
implements IOTPChannelStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @param IOTPTypeBuilderStrategy $typeBuilderStrategy
|
||||
* @param OAuth2OTP $otp
|
||||
* @return bool
|
||||
*/
|
||||
public function send(IOTPTypeBuilderStrategy $typeBuilderStrategy, OAuth2OTP $otp): bool
|
||||
{
|
||||
$value = $typeBuilderStrategy->generate($otp);
|
||||
// send email
|
||||
try{
|
||||
Mail::queue
|
||||
(
|
||||
new OAuth2PasswordlessOTPMail
|
||||
(
|
||||
$otp->getUserName(),
|
||||
$value,
|
||||
$otp->getLifetime()
|
||||
)
|
||||
);
|
||||
}
|
||||
catch (\Exception $ex){
|
||||
Log::error($ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\Exceptions\InvalidOAuth2Request;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
/**
|
||||
* Class OTPChannelStrategyFactory
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
final class OTPChannelStrategyFactory
|
||||
{
|
||||
public static function build(string $connection):IOTPChannelStrategy{
|
||||
|
||||
switch($connection){
|
||||
case OAuth2Protocol::OAuth2PasswordlessConnectionEmail:
|
||||
return new OTPChannelEmailStrategy();
|
||||
}
|
||||
throw new InvalidOAuth2Request(sprintf("connection value %s is not valid", $connection));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\Exceptions\InvalidOAuth2Request;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
/**
|
||||
* Class OTPTypeBuilderStrategyFactory
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
final class OTPTypeBuilderStrategyFactory
|
||||
{
|
||||
/**
|
||||
* @param string $send
|
||||
* @return IOTPTypeBuilderStrategy
|
||||
*/
|
||||
public static function build(string $send):IOTPTypeBuilderStrategy{
|
||||
switch($send){
|
||||
case OAuth2Protocol::OAuth2PasswordlessSendCode:
|
||||
return new OTPTypeCodeBuilderStrategy();
|
||||
}
|
||||
throw new InvalidOAuth2Request(sprintf("send value %s is not valid", $send));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php namespace App\Strategies\OTP;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
/**
|
||||
* Class OTPTypeCodeBuilderStrategy
|
||||
* @package App\Strategies\OTP
|
||||
*/
|
||||
final class OTPTypeCodeBuilderStrategy
|
||||
implements IOTPTypeBuilderStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otp
|
||||
* @return string
|
||||
*/
|
||||
public function generate(OAuth2OTP $otp): string
|
||||
{
|
||||
return $otp->getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php namespace OAuth2\Exceptions;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use OAuth2\OAuth2Protocol;
|
||||
/**
|
||||
* Class InvalidOTPException
|
||||
* @package App\libs\OAuth2\Exceptions
|
||||
*/
|
||||
class InvalidOTPException extends OAuth2BaseException
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return OAuth2Protocol::OAuth2Protocol_Error_InvalidOTP;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
<?php namespace OAuth2\Services;
|
||||
|
||||
<?php namespace OAuth2\Exceptions;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -12,24 +11,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Utils\Model\Identifier;
|
||||
use Utils\Services\UniqueIdentifierGenerator;
|
||||
use Zend\Math\Rand;
|
||||
|
||||
/**
|
||||
* Class OAuth2TokenGenerator
|
||||
* @package OAuth2\Services
|
||||
* Class InvalidRedeemOTPException
|
||||
* @package OAuth2\Exceptions
|
||||
*/
|
||||
class OAuth2TokenGenerator extends UniqueIdentifierGenerator
|
||||
final class InvalidRedeemOTPException extends OAuth2BaseException
|
||||
{
|
||||
/**
|
||||
* @param Identifier $identifier
|
||||
* @return Identifier
|
||||
* @return string
|
||||
*/
|
||||
protected function _generate(Identifier $identifier)
|
||||
public function getError()
|
||||
{
|
||||
return $identifier->setValue(Rand::getString($identifier->getLenght(), OAuth2Protocol::VsChar, true));
|
||||
return OAuth2Protocol::OAuth2Protocol_Error_InvalidGrant;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||
use OAuth2\Exceptions\InvalidAuthenticationRequestException;
|
||||
use OAuth2\Exceptions\InvalidAuthorizationRequestException;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
|
@ -33,8 +35,14 @@ final class OAuth2AuthorizationRequestFactory
|
|||
|
||||
$auth_request = new OAuth2AuthorizationRequest($msg);
|
||||
$scope = $auth_request->getScope();
|
||||
$response_type = $auth_request->getResponseType();
|
||||
|
||||
if($response_type == OAuth2Protocol::OAuth2Protocol_ResponseType_OTP){
|
||||
return new OAuth2PasswordlessAuthenticationRequest($auth_request);
|
||||
}
|
||||
|
||||
if(!is_null($scope) && str_contains($scope, OAuth2Protocol::OpenIdConnect_Scope) ) {
|
||||
$auth_request = new OAuth2AuthenticationRequest($auth_request);
|
||||
return new OAuth2AuthenticationRequest($auth_request);
|
||||
}
|
||||
|
||||
return $auth_request;
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
<?php namespace OAuth2\GrantTypes;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Exceptions\InvalidApplicationType;
|
||||
use OAuth2\Exceptions\InvalidClientException;
|
||||
use OAuth2\Exceptions\InvalidOAuth2Request;
|
||||
use OAuth2\Exceptions\InvalidOTPException;
|
||||
use OAuth2\Exceptions\InvalidRedeemOTPException;
|
||||
use OAuth2\Exceptions\LockedClientException;
|
||||
use OAuth2\Exceptions\OAuth2BaseException;
|
||||
use OAuth2\Exceptions\ScopeNotAllowedException;
|
||||
use OAuth2\Exceptions\UnAuthorizedClientException;
|
||||
use OAuth2\Exceptions\UriNotAllowedException;
|
||||
use OAuth2\Models\IClient;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use OAuth2\Repositories\IClientRepository;
|
||||
use OAuth2\Repositories\IServerPrivateKeyRepository;
|
||||
use OAuth2\Requests\OAuth2AccessTokenRequestPasswordless;
|
||||
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
||||
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||
use OAuth2\Requests\OAuth2Request;
|
||||
use OAuth2\Requests\OAuth2TokenRequest;
|
||||
use OAuth2\Responses\OAuth2AccessTokenResponse;
|
||||
use OAuth2\Responses\OAuth2DirectErrorResponse;
|
||||
use OAuth2\Responses\OAuth2IdTokenResponse;
|
||||
use OAuth2\Responses\OAuth2PasswordlessAuthenticationResponse;
|
||||
use OAuth2\Responses\OAuth2Response;
|
||||
use OAuth2\Services\IApiScopeService;
|
||||
use OAuth2\Services\IClientJWKSetReader;
|
||||
use OAuth2\Services\IClientService;
|
||||
use OAuth2\Services\IMementoOAuth2SerializerService;
|
||||
use OAuth2\Services\IPrincipalService;
|
||||
use OAuth2\Services\ISecurityContextService;
|
||||
use OAuth2\Services\ITokenService;
|
||||
use OAuth2\Services\IUserConsentService;
|
||||
use OAuth2\Strategies\IOAuth2AuthenticationStrategy;
|
||||
use Utils\Services\IAuthService;
|
||||
use Utils\Services\ILogService;
|
||||
|
||||
/**
|
||||
* Class PasswordlessGrantType
|
||||
* @package OAuth2\GrantTypes
|
||||
*/
|
||||
class PasswordlessGrantType extends InteractiveGrantType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $client = null;
|
||||
|
||||
/**
|
||||
* PasswordlessGrantType constructor.
|
||||
* @param IApiScopeService $scope_service
|
||||
* @param IClientService $client_service
|
||||
* @param IClientRepository $client_repository
|
||||
* @param ITokenService $token_service
|
||||
* @param IAuthService $auth_service
|
||||
* @param IOAuth2AuthenticationStrategy $auth_strategy
|
||||
* @param ILogService $log_service
|
||||
* @param IUserConsentService $user_consent_service
|
||||
* @param IMementoOAuth2SerializerService $memento_service
|
||||
* @param ISecurityContextService $security_context_service
|
||||
* @param IPrincipalService $principal_service
|
||||
* @param IServerPrivateKeyRepository $server_private_key_repository
|
||||
* @param IClientJWKSetReader $jwk_set_reader_service
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
IApiScopeService $scope_service,
|
||||
IClientService $client_service,
|
||||
IClientRepository $client_repository,
|
||||
ITokenService $token_service,
|
||||
IAuthService $auth_service,
|
||||
IOAuth2AuthenticationStrategy $auth_strategy,
|
||||
ILogService $log_service,
|
||||
IUserConsentService $user_consent_service,
|
||||
IMementoOAuth2SerializerService $memento_service,
|
||||
ISecurityContextService $security_context_service,
|
||||
IPrincipalService $principal_service,
|
||||
IServerPrivateKeyRepository $server_private_key_repository,
|
||||
IClientJWKSetReader $jwk_set_reader_service
|
||||
)
|
||||
{
|
||||
|
||||
parent::__construct
|
||||
(
|
||||
$client_service,
|
||||
$client_repository,
|
||||
$token_service,
|
||||
$log_service,
|
||||
$security_context_service,
|
||||
$principal_service,
|
||||
$auth_service,
|
||||
$user_consent_service,
|
||||
$scope_service,
|
||||
$auth_strategy,
|
||||
$memento_service,
|
||||
$server_private_key_repository,
|
||||
$jwk_set_reader_service
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function canHandle(OAuth2Request $request)
|
||||
{
|
||||
// 2 steps flow
|
||||
// start flow
|
||||
if
|
||||
(
|
||||
$request instanceof OAuth2PasswordlessAuthenticationRequest &&
|
||||
$request->isValid() &&
|
||||
OAuth2Protocol::responseTypeBelongsToFlow
|
||||
(
|
||||
$request->getResponseType(false),
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// complete flow
|
||||
$request = $this->buildTokenRequest($request);
|
||||
if
|
||||
(
|
||||
!is_null($request) &&
|
||||
$request instanceof OAuth2AccessTokenRequestPasswordless &&
|
||||
$request->isValid() &&
|
||||
$request->getGrantType() == $this->getType()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getType()
|
||||
{
|
||||
return OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless;
|
||||
}
|
||||
|
||||
public function getResponseType()
|
||||
{
|
||||
return OAuth2Protocol::getValidResponseTypes(OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless);
|
||||
}
|
||||
|
||||
protected function checkClientTypeAccess(IClient $client)
|
||||
{
|
||||
if (!$client->isPasswordlessEnabled()) {
|
||||
throw new InvalidApplicationType
|
||||
(
|
||||
sprintf
|
||||
(
|
||||
"client id %s must have Passwordless enabled",
|
||||
$client->getClientId(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2AuthorizationRequest $request
|
||||
* @param bool $has_former_consent
|
||||
* @return OAuth2PasswordlessAuthenticationResponse|OAuth2Response
|
||||
* @throws InvalidOAuth2Request
|
||||
*/
|
||||
protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent)
|
||||
{
|
||||
if (!($request instanceof OAuth2PasswordlessAuthenticationRequest)) {
|
||||
throw new InvalidOAuth2Request;
|
||||
}
|
||||
|
||||
$otp = $this->token_service->createOTPFromRequest($request, $this->client);
|
||||
|
||||
return new OAuth2PasswordlessAuthenticationResponse
|
||||
(
|
||||
$otp->getLength(),
|
||||
$otp->getRemainingLifetime(),
|
||||
$otp->getScopes()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2Request $request
|
||||
* @return OAuth2Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(OAuth2Request $request)
|
||||
{
|
||||
try {
|
||||
|
||||
if (!($request instanceof OAuth2PasswordlessAuthenticationRequest)) {
|
||||
throw new InvalidOAuth2Request;
|
||||
}
|
||||
|
||||
$client_id = $request->getClientId();
|
||||
$this->client = $this->client_repository->getClientById($client_id);
|
||||
|
||||
if (is_null($this->client)) {
|
||||
throw new InvalidClientException
|
||||
(
|
||||
sprintf
|
||||
(
|
||||
"client_id %s does not exists!",
|
||||
$client_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->client->isActive() || $this->client->isLocked()) {
|
||||
throw new LockedClientException
|
||||
(
|
||||
sprintf
|
||||
(
|
||||
'client id %s is locked',
|
||||
$client_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->checkClientTypeAccess($this->client);
|
||||
|
||||
//check redirect uri
|
||||
$redirect_uri = $request->getRedirectUri();
|
||||
|
||||
if (!empty($redirect_uri) && !$this->client->isUriAllowed($redirect_uri)) {
|
||||
throw new UriNotAllowedException
|
||||
(
|
||||
$redirect_uri
|
||||
);
|
||||
}
|
||||
|
||||
//check requested scope
|
||||
$scope = $request->getScope();
|
||||
$this->log_service->debug_msg(sprintf("scope %s", $scope));
|
||||
if (empty($scope) || !$this->client->isScopeAllowed($scope)) {
|
||||
throw new ScopeNotAllowedException($scope);
|
||||
}
|
||||
|
||||
$response = $this->buildResponse($request, false);
|
||||
// clear save data ...
|
||||
$this->auth_service->clearUserAuthorizationResponse();
|
||||
$this->memento_service->forget();
|
||||
return $response;
|
||||
|
||||
}
|
||||
catch(OAuth2BaseException $ex){
|
||||
$this->log_service->warning($ex);
|
||||
// clear save data ...
|
||||
$this->auth_service->clearUserAuthorizationResponse();
|
||||
$this->memento_service->forget();
|
||||
return new OAuth2DirectErrorResponse($ex->getError(), $ex->getMessage());
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
// clear save data ...
|
||||
$this->auth_service->clearUserAuthorizationResponse();
|
||||
$this->memento_service->forget();
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements last request processing for Authorization code (Access Token Request processing)
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4.1.3 and
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
* @param OAuth2Request $request
|
||||
* @return OAuth2AccessTokenResponse
|
||||
* @throws \Exception
|
||||
* @throws InvalidClientException
|
||||
* @throws UnAuthorizedClientException
|
||||
* @throws UriNotAllowedException
|
||||
*/
|
||||
public function completeFlow(OAuth2Request $request)
|
||||
{
|
||||
try {
|
||||
|
||||
if (!($request instanceof OAuth2AccessTokenRequestPasswordless)) {
|
||||
throw new InvalidOAuth2Request;
|
||||
}
|
||||
|
||||
parent::completeFlow($request);
|
||||
|
||||
$this->client = $this->client_auth_context->getClient();
|
||||
$this->checkClientTypeAccess($this->client);
|
||||
$otp = OAuth2OTP::fromRequest($request, $this->client->getOtpLength());
|
||||
|
||||
$access_token = $this->token_service->createAccessTokenFromOTP
|
||||
(
|
||||
$otp,
|
||||
$this->client
|
||||
);
|
||||
|
||||
$this->principal_service->register
|
||||
(
|
||||
$otp->getUserId(),
|
||||
$otp->getAuthTime()
|
||||
);
|
||||
|
||||
$id_token = $this->token_service->createIdToken
|
||||
(
|
||||
$otp->getNonce(),
|
||||
$this->client->getClientId(),
|
||||
$access_token
|
||||
);
|
||||
|
||||
$refresh_token = $access_token->getRefreshToken();
|
||||
|
||||
if (!is_null($access_token))
|
||||
$refresh_token = $access_token->getRefreshToken();
|
||||
|
||||
$response = new OAuth2IdTokenResponse
|
||||
(
|
||||
is_null($access_token) ? null : $access_token->getValue(),
|
||||
is_null($access_token) ? null : $access_token->getLifetime(),
|
||||
is_null($id_token) ? null : $id_token->toCompactSerialization(),
|
||||
is_null($refresh_token) ? null : $refresh_token->getValue()
|
||||
);
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($otp->getUserName());
|
||||
|
||||
// emmit login
|
||||
Auth::login($user, false);
|
||||
|
||||
$this->security_context_service->clear();
|
||||
|
||||
return $response;
|
||||
} catch (InvalidOTPException $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->security_context_service->clear();
|
||||
throw new InvalidRedeemOTPException
|
||||
(
|
||||
$ex->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param OAuth2Request $request
|
||||
* @return OAuth2AccessTokenRequestPasswordless|OAuth2Response|null
|
||||
*/
|
||||
public function buildTokenRequest(OAuth2Request $request)
|
||||
{
|
||||
if ($request instanceof OAuth2TokenRequest)
|
||||
{
|
||||
if ($request->getGrantType() !== $this->getType())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new OAuth2AccessTokenRequestPasswordless($request->getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Zend\Math\Rand;
|
||||
/**
|
||||
* Class AccessToken
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-1.4
|
||||
|
@ -23,6 +26,11 @@ class AccessToken extends Token {
|
|||
*/
|
||||
private $auth_code;
|
||||
|
||||
/**
|
||||
* @var OAuth2OTP
|
||||
*/
|
||||
private $otp;
|
||||
|
||||
/**
|
||||
* @var RefreshToken
|
||||
*/
|
||||
|
@ -40,7 +48,7 @@ class AccessToken extends Token {
|
|||
* @param int $lifetime
|
||||
* @return AccessToken
|
||||
*/
|
||||
public static function create(AuthorizationCode $auth_code, $lifetime = 3600){
|
||||
public static function create(AuthorizationCode $auth_code, $lifetime = 3600){
|
||||
$instance = new self();
|
||||
$instance->user_id = $auth_code->getUserId();
|
||||
$instance->scope = $auth_code->getScope();
|
||||
|
@ -53,6 +61,25 @@ class AccessToken extends Token {
|
|||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otp
|
||||
* @param string $client_id
|
||||
* @param string $audience
|
||||
* @param int $lifetime
|
||||
* @return AccessToken
|
||||
*/
|
||||
public static function createFromOTP(OAuth2OTP $otp,string $client_id, string $audience, $lifetime = 3600){
|
||||
$instance = new self();
|
||||
$instance->otp = $otp;
|
||||
$instance->scope = $otp->getScopes();
|
||||
// client id (oauth2) not client identifier
|
||||
$instance->client_id = $client_id;
|
||||
$instance->audience = $audience;
|
||||
$instance->lifetime = intval($lifetime);
|
||||
$instance->is_hashed = false;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public static function createFromParams($scope, $client_id, $audience,$user_id,$lifetime){
|
||||
$instance = new self();
|
||||
$instance->scope = $scope;
|
||||
|
@ -130,7 +157,7 @@ class AccessToken extends Token {
|
|||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
public function getType():string
|
||||
{
|
||||
return 'access_token';
|
||||
}
|
||||
|
@ -142,4 +169,29 @@ class AccessToken extends Token {
|
|||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OAuth2OTP
|
||||
*/
|
||||
public function getOtp(): ?OAuth2OTP
|
||||
{
|
||||
return $this->otp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
if(!is_null($this->otp)){
|
||||
$this->user_id = $this->otp->getUserId();
|
||||
}
|
||||
return intval($this->user_id);
|
||||
}
|
||||
|
||||
public function generateValue(): string
|
||||
{
|
||||
$this->value = Rand::getString($this->len, OAuth2Protocol::VsChar);
|
||||
return $this->value;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
use Utils\IPHelper;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Zend\Math\Rand;
|
||||
|
||||
/**
|
||||
* Class AuthorizationCode
|
||||
|
@ -313,7 +314,7 @@ class AuthorizationCode extends Token
|
|||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
public function getType():string
|
||||
{
|
||||
return 'auth_code';
|
||||
}
|
||||
|
@ -384,4 +385,9 @@ class AuthorizationCode extends Token
|
|||
return $this->code_challenge_method;
|
||||
}
|
||||
|
||||
public function generateValue(): string
|
||||
{
|
||||
$this->value = Rand::getString($this->len, OAuth2Protocol::VsChar);
|
||||
return $this->value;
|
||||
}
|
||||
}
|
|
@ -324,4 +324,9 @@ interface IClient extends IEntity
|
|||
* @return bool
|
||||
*/
|
||||
public function isPKCEEnabled():bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPasswordlessEnabled():bool;
|
||||
}
|
|
@ -11,7 +11,11 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Utils\IPHelper;
|
||||
use Zend\Math\Rand;
|
||||
|
||||
/**
|
||||
* Class RefreshToken
|
||||
* @see http://tools.ietf.org/html/rfc6749#section-1.5
|
||||
|
@ -81,7 +85,7 @@ class RefreshToken extends Token {
|
|||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
public function getType():string
|
||||
{
|
||||
return 'refresh_token';
|
||||
}
|
||||
|
@ -93,4 +97,10 @@ class RefreshToken extends Token {
|
|||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function generateValue(): string
|
||||
{
|
||||
$this->value = Rand::getString($this->len, OAuth2Protocol::VsChar);
|
||||
return $this->value;
|
||||
}
|
||||
}
|
|
@ -15,13 +15,13 @@ use DateInterval;
|
|||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Utils\IPHelper;
|
||||
use Utils\Model\Identifier;
|
||||
use Utils\Model\AbstractIdentifier;
|
||||
/**
|
||||
* Class Token
|
||||
* Defines the common behavior for all emitted tokens
|
||||
* @package OAuth2\Models
|
||||
*/
|
||||
abstract class Token extends Identifier
|
||||
abstract class Token extends AbstractIdentifier
|
||||
{
|
||||
|
||||
const DefaultByteLength = 32;
|
||||
|
@ -54,6 +54,9 @@ abstract class Token extends Identifier
|
|||
* @var bool
|
||||
*/
|
||||
protected $is_hashed;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $user_id;
|
||||
|
||||
public function __construct($len = self::DefaultByteLength)
|
||||
|
@ -141,4 +144,5 @@ abstract class Token extends Identifier
|
|||
|
||||
|
||||
public abstract function fromJSON($json);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\Http\Utils\UserIPHelperProvider;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -38,6 +39,7 @@ use OAuth2\GrantTypes\AuthorizationCodeGrantType;
|
|||
use OAuth2\GrantTypes\ClientCredentialsGrantType;
|
||||
use OAuth2\GrantTypes\HybridGrantType;
|
||||
use OAuth2\GrantTypes\ImplicitGrantType;
|
||||
use OAuth2\GrantTypes\PasswordlessGrantType;
|
||||
use OAuth2\GrantTypes\RefreshBearerTokenGrantType;
|
||||
use OAuth2\Models\IClient;
|
||||
use OAuth2\Repositories\IClientRepository;
|
||||
|
@ -64,6 +66,7 @@ use utils\factories\BasicJWTFactory;
|
|||
use Utils\Services\IAuthService;
|
||||
use Utils\Services\ICheckPointService;
|
||||
use Utils\Services\ILogService;
|
||||
|
||||
/**
|
||||
* Class OAuth2Protocol
|
||||
* Implementation of @see http://tools.ietf.org/html/rfc6749
|
||||
|
@ -76,21 +79,23 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* @var OAuth2Request
|
||||
*/
|
||||
private $last_request = null;
|
||||
const OAuth2Protocol_Scope_Delimiter = ' ';
|
||||
const OAuth2Protocol_Scope_Delimiter = ' ';
|
||||
const OAuth2Protocol_ResponseType_Delimiter = ' ';
|
||||
|
||||
const OAuth2Protocol_GrantType_AuthCode = 'authorization_code';
|
||||
const OAuth2Protocol_GrantType_Implicit = 'implicit';
|
||||
const OAuth2Protocol_GrantType_Hybrid = 'hybrid';
|
||||
const OAuth2Protocol_GrantType_AuthCode = 'authorization_code';
|
||||
const OAuth2Protocol_GrantType_Passwordless = 'passwordless';
|
||||
const OAuth2Protocol_GrantType_Implicit = 'implicit';
|
||||
const OAuth2Protocol_GrantType_Hybrid = 'hybrid';
|
||||
|
||||
const OAuth2Protocol_GrantType_ResourceOwner_Password = 'password';
|
||||
const OAuth2Protocol_GrantType_ClientCredentials = 'client_credentials';
|
||||
const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token';
|
||||
const OAuth2Protocol_GrantType_ClientCredentials = 'client_credentials';
|
||||
const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token';
|
||||
|
||||
const OAuth2Protocol_ResponseType_Code = 'code';
|
||||
const OAuth2Protocol_ResponseType_Token = 'token';
|
||||
const OAuth2Protocol_ResponseType_Code = 'code';
|
||||
const OAuth2Protocol_ResponseType_OTP = 'otp';
|
||||
const OAuth2Protocol_ResponseType_Token = 'token';
|
||||
const OAuth2Protocol_ResponseType_IdToken = 'id_token';
|
||||
const OAuth2Protocol_ResponseType_None = 'none';
|
||||
const OAuth2Protocol_ResponseType_None = 'none';
|
||||
|
||||
/**
|
||||
* The OAuth 2.0 specification allows for registration of space-separated response_type parameter values. If a
|
||||
|
@ -110,7 +115,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* In this mode, Authorization Response parameters are encoded in the query string added to the redirect_uri when
|
||||
* redirecting back to the Client.
|
||||
*/
|
||||
const OAuth2Protocol_ResponseMode_Query = 'query';
|
||||
const OAuth2Protocol_ResponseMode_Query = 'query';
|
||||
|
||||
/**
|
||||
* In this mode, Authorization Response parameters are encoded in the fragment added to the redirect_uri when
|
||||
|
@ -127,9 +132,9 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* is intended to be used only once, the Authorization Server MUST instruct the User Agent (and any intermediaries)
|
||||
* not to store or reuse the content of the response.
|
||||
*/
|
||||
const OAuth2Protocol_ResponseMode_FormPost = 'form_post';
|
||||
const OAuth2Protocol_ResponseMode_FormPost = 'form_post';
|
||||
|
||||
const OAuth2Protocol_ResponseMode_Direct = 'direct';
|
||||
const OAuth2Protocol_ResponseMode_Direct = 'direct';
|
||||
|
||||
|
||||
static public $valid_response_modes = array
|
||||
|
@ -155,21 +160,21 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
static public function getDefaultResponseMode(array $response_type)
|
||||
{
|
||||
|
||||
if(count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Code))) === 0)
|
||||
if (count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Code))) === 0)
|
||||
return self::OAuth2Protocol_ResponseMode_Query;
|
||||
|
||||
if(count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Token))) === 0)
|
||||
if (count(array_diff($response_type, array(self::OAuth2Protocol_ResponseType_Token))) === 0)
|
||||
return self::OAuth2Protocol_ResponseMode_Fragment;
|
||||
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations
|
||||
if(count(array_diff($response_type, array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_Token
|
||||
)
|
||||
if (count(array_diff($response_type, array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_Token
|
||||
)
|
||||
)) === 0)
|
||||
return self::OAuth2Protocol_ResponseMode_Fragment;
|
||||
return self::OAuth2Protocol_ResponseMode_Fragment;
|
||||
|
||||
if(count(array_diff($response_type, array
|
||||
if (count(array_diff($response_type, array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_IdToken
|
||||
|
@ -177,7 +182,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
)) === 0)
|
||||
return self::OAuth2Protocol_ResponseMode_Fragment;
|
||||
|
||||
if(count(array_diff($response_type, array
|
||||
if (count(array_diff($response_type, array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Token,
|
||||
self::OAuth2Protocol_ResponseType_IdToken
|
||||
|
@ -185,7 +190,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
)) === 0)
|
||||
return self::OAuth2Protocol_ResponseMode_Fragment;
|
||||
|
||||
if(count(array_diff($response_type, array
|
||||
if (count(array_diff($response_type, array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_Token,
|
||||
|
@ -197,21 +202,43 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
}
|
||||
|
||||
|
||||
const OAuth2Protocol_ClientId = 'client_id';
|
||||
const OAuth2Protocol_UserId = 'user_id';
|
||||
const OAuth2Protocol_ClientId = 'client_id';
|
||||
const OAuth2Protocol_UserId = 'user_id';
|
||||
const OAuth2Protocol_ClientSecret = 'client_secret';
|
||||
const OAuth2Protocol_Token = 'token';
|
||||
const OAuth2Protocol_TokenType = 'token_type';
|
||||
const OAuth2Protocol_Token = 'token';
|
||||
const OAuth2Protocol_TokenType = 'token_type';
|
||||
|
||||
// http://tools.ietf.org/html/rfc7009#section-2.1
|
||||
const OAuth2Protocol_TokenType_Hint = 'token_type_hint';
|
||||
const OAuth2Protocol_TokenType_Hint = 'token_type_hint';
|
||||
const OAuth2Protocol_AccessToken_ExpiresIn = 'expires_in';
|
||||
const OAuth2Protocol_RefreshToken = 'refresh_token';
|
||||
const OAuth2Protocol_AccessToken = 'access_token';
|
||||
const OAuth2Protocol_RedirectUri = 'redirect_uri';
|
||||
const OAuth2Protocol_Scope = 'scope';
|
||||
const OAuth2Protocol_Audience = 'audience';
|
||||
const OAuth2Protocol_State = 'state';
|
||||
const OAuth2Protocol_RefreshToken = 'refresh_token';
|
||||
const OAuth2Protocol_AccessToken = 'access_token';
|
||||
const OAuth2Protocol_RedirectUri = 'redirect_uri';
|
||||
const OAuth2Protocol_Scope = 'scope';
|
||||
const OAuth2Protocol_Audience = 'audience';
|
||||
const OAuth2Protocol_State = 'state';
|
||||
|
||||
// passwordless
|
||||
|
||||
const OAuth2PasswordlessConnection = 'connection';
|
||||
const OAuth2PasswordlessConnectionSMS = 'sms';
|
||||
const OAuth2PasswordlessConnectionEmail = 'email';
|
||||
const ValidOAuth2PasswordlessConnectionValues = [
|
||||
self::OAuth2PasswordlessConnectionSMS,
|
||||
self::OAuth2PasswordlessConnectionEmail
|
||||
];
|
||||
|
||||
const OAuth2PasswordlessSend = 'send';
|
||||
const OAuth2PasswordlessSendCode = 'code';
|
||||
const OAuth2PasswordlessSendLink = 'link';
|
||||
|
||||
const ValidOAuth2PasswordlessSendValues = [
|
||||
self::OAuth2PasswordlessSendCode,
|
||||
self::OAuth2PasswordlessSendLink,
|
||||
];
|
||||
|
||||
const OAuth2PasswordlessEmail = 'email';
|
||||
const OAuth2PasswordlessPhoneNumber = 'phone_number';
|
||||
|
||||
/**
|
||||
* @see http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions
|
||||
|
@ -223,13 +250,13 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* JSON string that represents the End-User's login state at the OP. It MUST NOT contain the space (" ") character.
|
||||
* This value is opaque to the RP. This is REQUIRED if session management is supported.
|
||||
*/
|
||||
const OAuth2Protocol_Session_State = 'session_state';
|
||||
const OAuth2Protocol_Session_State = 'session_state';
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
|
||||
// ID Token value associated with the authenticated session.
|
||||
const OAuth2Protocol_IdToken = 'id_token';
|
||||
const OAuth2Protocol_IdToken = 'id_token';
|
||||
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||
const OAuth2Protocol_Nonce = 'nonce';
|
||||
const OAuth2Protocol_Nonce = 'nonce';
|
||||
|
||||
/**
|
||||
* custom param - social login
|
||||
|
@ -242,7 +269,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL.
|
||||
* (The auth_time Claim semantically corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] auth_time response parameter.)
|
||||
*/
|
||||
const OAuth2Protocol_AuthTime = 'auth_time';
|
||||
const OAuth2Protocol_AuthTime = 'auth_time';
|
||||
|
||||
/**
|
||||
* Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of
|
||||
|
@ -267,35 +294,35 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* Specifies how the Authorization Server displays the authentication and consent user interface pages to
|
||||
* the End-User.
|
||||
*/
|
||||
const OAuth2Protocol_Display ='display';
|
||||
const OAuth2Protocol_Display = 'display';
|
||||
/**
|
||||
* The Authorization Server SHOULD display the authentication and consent UI consistent with a full User Agent page
|
||||
* view. If the display parameter is not specified, this is the default display mode.
|
||||
* The Authorization Server MAY also attempt to detect the capabilities of the User Agent and present an
|
||||
* appropriate display.
|
||||
*/
|
||||
const OAuth2Protocol_Display_Page ='page';
|
||||
const OAuth2Protocol_Display_Page = 'page';
|
||||
/**
|
||||
* The Authorization Server SHOULD display the authentication and consent UI consistent with a popup User Agent
|
||||
* window. The popup User Agent window should be of an appropriate size for a login-focused dialog and should not
|
||||
* obscure the entire window that it is popping up over.
|
||||
*/
|
||||
const OAuth2Protocol_Display_PopUp ='popup';
|
||||
const OAuth2Protocol_Display_PopUp = 'popup';
|
||||
/**
|
||||
* The Authorization Server SHOULD display the authentication and consent UI consistent with a device that leverages
|
||||
* a touch interface.
|
||||
*/
|
||||
const OAuth2Protocol_Display_Touch ='touch';
|
||||
const OAuth2Protocol_Display_Touch = 'touch';
|
||||
/**
|
||||
* The Authorization Server SHOULD display the authentication and consent UI consistent with a "feature phone"
|
||||
* type display.
|
||||
*/
|
||||
const OAuth2Protocol_Display_Wap ='wap';
|
||||
const OAuth2Protocol_Display_Wap = 'wap';
|
||||
|
||||
/**
|
||||
* Extension: display the login/consent interaction like a json doc
|
||||
*/
|
||||
const OAuth2Protocol_Display_Native ='native';
|
||||
const OAuth2Protocol_Display_Native = 'native';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
@ -363,14 +390,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
static public function getValidResponseTypes($flow = 'all')
|
||||
{
|
||||
$code_flow = array
|
||||
(
|
||||
$code_flow = [
|
||||
//OAuth2 / OIDC
|
||||
array
|
||||
(
|
||||
[
|
||||
self::OAuth2Protocol_ResponseType_Code
|
||||
)
|
||||
);
|
||||
]
|
||||
];
|
||||
|
||||
$implicit_flow = array
|
||||
(
|
||||
|
@ -386,17 +411,17 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
),
|
||||
array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_IdToken ,
|
||||
self::OAuth2Protocol_ResponseType_IdToken,
|
||||
self::OAuth2Protocol_ResponseType_Token
|
||||
)
|
||||
);
|
||||
|
||||
$hybrid_flow = array
|
||||
$hybrid_flow = array
|
||||
(
|
||||
array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_IdToken
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_IdToken
|
||||
),
|
||||
array
|
||||
(
|
||||
|
@ -405,13 +430,13 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
),
|
||||
array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code ,
|
||||
self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_IdToken,
|
||||
self::OAuth2Protocol_ResponseType_Token
|
||||
)
|
||||
);
|
||||
|
||||
if($flow === 'all')
|
||||
if ($flow === 'all')
|
||||
return array_merge
|
||||
(
|
||||
$code_flow,
|
||||
|
@ -419,13 +444,20 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$hybrid_flow
|
||||
);
|
||||
|
||||
if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)
|
||||
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless)
|
||||
return [
|
||||
[
|
||||
self::OAuth2Protocol_ResponseType_OTP
|
||||
]
|
||||
];
|
||||
|
||||
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)
|
||||
return $code_flow;
|
||||
|
||||
if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Implicit)
|
||||
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Implicit)
|
||||
return $implicit_flow;
|
||||
|
||||
if($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid)
|
||||
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid)
|
||||
return $hybrid_flow;
|
||||
|
||||
return [];
|
||||
|
@ -446,26 +478,25 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
{
|
||||
if
|
||||
(
|
||||
!in_array
|
||||
(
|
||||
$flow, array
|
||||
(
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Implicit,
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid,
|
||||
'all'
|
||||
)
|
||||
)
|
||||
!in_array
|
||||
(
|
||||
$flow, [
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Implicit,
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid,
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
'all'
|
||||
]
|
||||
)
|
||||
return false;
|
||||
)
|
||||
return false;
|
||||
|
||||
$flow_response_types = self::getValidResponseTypes($flow);
|
||||
|
||||
foreach($flow_response_types as $rt)
|
||||
{
|
||||
if(count($rt) !== count($response_type)) continue;
|
||||
$diff = array_diff($rt, $response_type);
|
||||
if(count($diff) === 0) return true;
|
||||
foreach ($flow_response_types as $rt) {
|
||||
if (count($rt) !== count($response_type)) continue;
|
||||
$diff = array_diff($rt, $response_type);
|
||||
if (count($diff) === 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -534,9 +565,9 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* through the sequence. If the value is force, then the user sees a consent page even if they
|
||||
* previously gave consent to your application for a given set of scopes.
|
||||
*/
|
||||
const OAuth2Protocol_Approval_Prompt = 'approval_prompt';
|
||||
const OAuth2Protocol_Approval_Prompt = 'approval_prompt';
|
||||
const OAuth2Protocol_Approval_Prompt_Force = 'force';
|
||||
const OAuth2Protocol_Approval_Prompt_Auto = 'auto';
|
||||
const OAuth2Protocol_Approval_Prompt_Auto = 'auto';
|
||||
|
||||
/**
|
||||
* Indicates whether your application needs to access an API when the user is not present at
|
||||
|
@ -544,8 +575,8 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* when the user is not present at the browser, then use offline. This will result in your application
|
||||
* obtaining a refresh token the first time your application exchanges an authorization code for a user.
|
||||
*/
|
||||
const OAuth2Protocol_AccessType = 'access_type';
|
||||
const OAuth2Protocol_AccessType_Online = 'online';
|
||||
const OAuth2Protocol_AccessType = 'access_type';
|
||||
const OAuth2Protocol_AccessType_Online = 'online';
|
||||
const OAuth2Protocol_AccessType_Offline = 'offline';
|
||||
|
||||
const OAuth2Protocol_GrantType = 'grant_type';
|
||||
|
@ -554,6 +585,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
const OAuth2Protocol_ErrorUri = 'error_uri';
|
||||
const OAuth2Protocol_Error_InvalidRequest = 'invalid_request';
|
||||
const OAuth2Protocol_Error_UnauthorizedClient = 'unauthorized_client';
|
||||
const OAuth2Protocol_Error_InvalidOTP = 'invalid_otp';
|
||||
const OAuth2Protocol_Error_RedirectUriMisMatch = 'redirect_uri_mismatch';
|
||||
const OAuth2Protocol_Error_AccessDenied = 'access_denied';
|
||||
const OAuth2Protocol_Error_UnsupportedResponseType = 'unsupported_response_type';
|
||||
|
@ -627,26 +659,26 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
|
||||
|
||||
const OAuth2Protocol_Error_Invalid_Recipient_Keys = 'invalid_recipient_keys';
|
||||
const OAuth2Protocol_Error_Invalid_Server_Keys = 'invalid_server_keys';
|
||||
const OAuth2Protocol_Error_Not_Found_Server_Keys = 'not_found_server_keys';
|
||||
const OAuth2Protocol_Error_Invalid_Server_Keys = 'invalid_server_keys';
|
||||
const OAuth2Protocol_Error_Not_Found_Server_Keys = 'not_found_server_keys';
|
||||
|
||||
|
||||
public static $valid_responses_types = array
|
||||
(
|
||||
self::OAuth2Protocol_ResponseType_Code => self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_Code => self::OAuth2Protocol_ResponseType_Code,
|
||||
self::OAuth2Protocol_ResponseType_Token => self::OAuth2Protocol_ResponseType_Token
|
||||
);
|
||||
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
|
||||
const TokenEndpoint_AuthMethod_ClientSecretBasic = 'client_secret_basic';
|
||||
const TokenEndpoint_AuthMethod_ClientSecretPost = 'client_secret_post';
|
||||
const TokenEndpoint_AuthMethod_ClientSecretJwt = 'client_secret_jwt';
|
||||
const TokenEndpoint_AuthMethod_PrivateKeyJwt = 'private_key_jwt';
|
||||
const TokenEndpoint_AuthMethod_None = 'none';
|
||||
const TokenEndpoint_AuthMethod_ClientSecretPost = 'client_secret_post';
|
||||
const TokenEndpoint_AuthMethod_ClientSecretJwt = 'client_secret_jwt';
|
||||
const TokenEndpoint_AuthMethod_PrivateKeyJwt = 'private_key_jwt';
|
||||
const TokenEndpoint_AuthMethod_None = 'none';
|
||||
|
||||
const OAuth2Protocol_ClientAssertionType = 'client_assertion_type';
|
||||
const OAuth2Protocol_ClientAssertion = 'client_assertion';
|
||||
const OAuth2Protocol_ClientAssertionType = 'client_assertion_type';
|
||||
const OAuth2Protocol_ClientAssertion = 'client_assertion';
|
||||
|
||||
public static $token_endpoint_auth_methods = array
|
||||
(
|
||||
|
@ -721,8 +753,8 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
|
||||
/**
|
||||
* PKCE
|
||||
* @see https://tools.ietf.org/html/rfc7636
|
||||
**/
|
||||
* @see https://tools.ietf.org/html/rfc7636
|
||||
**/
|
||||
|
||||
// auth request new params
|
||||
|
||||
|
@ -835,31 +867,48 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
public function __construct
|
||||
(
|
||||
ILogService $log_service,
|
||||
ILogService $log_service,
|
||||
IClientService $client_service,
|
||||
IClientRepository $client_repository,
|
||||
ITokenService $token_service,
|
||||
IAuthService $auth_service,
|
||||
ITokenService $token_service,
|
||||
IAuthService $auth_service,
|
||||
IOAuth2AuthenticationStrategy $auth_strategy,
|
||||
ICheckPointService $checkpoint_service,
|
||||
IApiScopeService $scope_service,
|
||||
IApiScopeService $scope_service,
|
||||
IUserConsentService $user_consent_service,
|
||||
IServerPrivateKeyRepository $server_private_keys_repository,
|
||||
IOpenIDProviderConfigurationService $oidc_provider_configuration_service,
|
||||
IMementoOAuth2SerializerService $memento_service,
|
||||
ISecurityContextService $security_context_service,
|
||||
IPrincipalService $principal_service,
|
||||
IServerPrivateKeyRepository $server_private_key_repository,
|
||||
IClientJWKSetReader $jwk_set_reader_service,
|
||||
UserIPHelperProvider $ip_helper
|
||||
ISecurityContextService $security_context_service,
|
||||
IPrincipalService $principal_service,
|
||||
IServerPrivateKeyRepository $server_private_key_repository,
|
||||
IClientJWKSetReader $jwk_set_reader_service,
|
||||
UserIPHelperProvider $ip_helper
|
||||
)
|
||||
{
|
||||
|
||||
$this->server_private_keys_repository = $server_private_keys_repository;
|
||||
$this->server_private_keys_repository = $server_private_keys_repository;
|
||||
$this->oidc_provider_configuration_service = $oidc_provider_configuration_service;
|
||||
$this->memento_service = $memento_service;
|
||||
$this->memento_service = $memento_service;
|
||||
|
||||
$authorization_code_grant_type = new AuthorizationCodeGrantType
|
||||
$passwordless_grant_type = new PasswordlessGrantType
|
||||
(
|
||||
$scope_service,
|
||||
$client_service,
|
||||
$client_repository,
|
||||
$token_service,
|
||||
$auth_service,
|
||||
$auth_strategy,
|
||||
$log_service,
|
||||
$user_consent_service,
|
||||
$this->memento_service,
|
||||
$security_context_service,
|
||||
$principal_service,
|
||||
$server_private_key_repository,
|
||||
$jwk_set_reader_service
|
||||
);
|
||||
|
||||
$authorization_code_grant_type = new AuthorizationCodeGrantType
|
||||
(
|
||||
$scope_service,
|
||||
$client_service,
|
||||
|
@ -910,7 +959,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$jwk_set_reader_service
|
||||
);
|
||||
|
||||
$refresh_bearer_token_grant_type = new RefreshBearerTokenGrantType
|
||||
$refresh_bearer_token_grant_type = new RefreshBearerTokenGrantType
|
||||
(
|
||||
$client_service,
|
||||
$client_repository,
|
||||
|
@ -918,7 +967,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$log_service
|
||||
);
|
||||
|
||||
$client_credential_grant_type = new ClientCredentialsGrantType
|
||||
$client_credential_grant_type = new ClientCredentialsGrantType
|
||||
(
|
||||
$scope_service,
|
||||
$client_service,
|
||||
|
@ -927,24 +976,26 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$log_service
|
||||
);
|
||||
|
||||
$this->grant_types[$authorization_code_grant_type->getType()] = $authorization_code_grant_type;
|
||||
$this->grant_types[$implicit_grant_type->getType()] = $implicit_grant_type;
|
||||
// setting grants collection
|
||||
$this->grant_types[$passwordless_grant_type->getType()] = $passwordless_grant_type;
|
||||
$this->grant_types[$authorization_code_grant_type->getType()] = $authorization_code_grant_type;
|
||||
$this->grant_types[$implicit_grant_type->getType()] = $implicit_grant_type;
|
||||
$this->grant_types[$refresh_bearer_token_grant_type->getType()] = $refresh_bearer_token_grant_type;
|
||||
$this->grant_types[$client_credential_grant_type->getType()] = $client_credential_grant_type;
|
||||
$this->grant_types[$hybrid_grant_type->getType()] = $hybrid_grant_type;
|
||||
$this->grant_types[$client_credential_grant_type->getType()] = $client_credential_grant_type;
|
||||
$this->grant_types[$hybrid_grant_type->getType()] = $hybrid_grant_type;
|
||||
|
||||
$this->log_service = $log_service;
|
||||
$this->checkpoint_service = $checkpoint_service;
|
||||
$this->client_service = $client_service;
|
||||
$this->client_repository = $client_repository;
|
||||
$this->auth_service = $auth_service;
|
||||
$this->principal_service = $principal_service;
|
||||
$this->token_service = $token_service;
|
||||
$this->log_service = $log_service;
|
||||
$this->checkpoint_service = $checkpoint_service;
|
||||
$this->client_service = $client_service;
|
||||
$this->client_repository = $client_repository;
|
||||
$this->auth_service = $auth_service;
|
||||
$this->principal_service = $principal_service;
|
||||
$this->token_service = $token_service;
|
||||
|
||||
$this->authorize_endpoint = new AuthorizationEndpoint($this);
|
||||
$this->token_endpoint = new TokenEndpoint($this);
|
||||
$this->revoke_endpoint = new TokenRevocationEndpoint($this, $client_service, $client_repository, $token_service, $log_service);
|
||||
$this->introspection_endpoint = new TokenIntrospectionEndpoint
|
||||
$this->authorize_endpoint = new AuthorizationEndpoint($this);
|
||||
$this->token_endpoint = new TokenEndpoint($this);
|
||||
$this->revoke_endpoint = new TokenRevocationEndpoint($this, $client_service, $client_repository, $token_service, $log_service);
|
||||
$this->introspection_endpoint = new TokenIntrospectionEndpoint
|
||||
(
|
||||
$this,
|
||||
$client_service,
|
||||
|
@ -964,14 +1015,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
public function authorize(OAuth2Request $request = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
$this->last_request = $request;
|
||||
|
||||
if (is_null($this->last_request)) throw new InvalidOAuth2Request;
|
||||
|
||||
if(!$this->last_request->isValid())
|
||||
{
|
||||
if (!$this->last_request->isValid()) {
|
||||
// then check if we have a memento ....
|
||||
if (!$this->memento_service->exists())
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
|
@ -981,20 +1030,16 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
OAuth2Message::buildFromMemento($this->memento_service->load())
|
||||
);
|
||||
|
||||
if(!$this->last_request->isValid())
|
||||
if (!$this->last_request->isValid())
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
|
||||
}
|
||||
return $this->authorize_endpoint->handle($this->last_request);
|
||||
}
|
||||
catch (UriNotAllowedException $ex1)
|
||||
{
|
||||
} catch (UriNotAllowedException $ex1) {
|
||||
$this->log_service->warning($ex1);
|
||||
$this->checkpoint_service->trackException($ex1);
|
||||
throw $ex1;
|
||||
}
|
||||
catch(OAuth2BaseException $ex2)
|
||||
{
|
||||
} catch (OAuth2BaseException $ex2) {
|
||||
$this->log_service->warning($ex2);
|
||||
$this->checkpoint_service->trackException($ex2);
|
||||
|
||||
|
@ -1010,8 +1055,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$ex2->getMessage(),
|
||||
$redirect_uri
|
||||
);
|
||||
}
|
||||
catch (AbsentClientException $ex3){
|
||||
} catch (AbsentClientException $ex3) {
|
||||
$this->log_service->warning($ex3);
|
||||
$this->checkpoint_service->trackException($ex3);
|
||||
|
||||
|
@ -1026,8 +1070,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$ex3->getMessage(),
|
||||
$redirect_uri
|
||||
);
|
||||
}
|
||||
catch (AbsentCurrentUserException $ex4){
|
||||
} catch (AbsentCurrentUserException $ex4) {
|
||||
$this->log_service->warning($ex4);
|
||||
$this->checkpoint_service->trackException($ex4);
|
||||
|
||||
|
@ -1042,9 +1085,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
$ex4->getMessage(),
|
||||
$redirect_uri
|
||||
);
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
} catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->checkpoint_service->trackException($ex);
|
||||
|
||||
|
@ -1068,27 +1109,22 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
public function token(OAuth2Request $request = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
$this->last_request = $request;
|
||||
|
||||
if (is_null($this->last_request))
|
||||
throw new InvalidOAuth2Request;
|
||||
|
||||
if(!$this->last_request->isValid())
|
||||
if (!$this->last_request->isValid())
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
|
||||
return $this->token_endpoint->handle($this->last_request);
|
||||
}
|
||||
catch(OAuth2BaseException $ex1)
|
||||
{
|
||||
} catch (OAuth2BaseException $ex1) {
|
||||
$this->log_service->warning($ex1);
|
||||
$this->checkpoint_service->trackException($ex1);
|
||||
|
||||
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
} catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->checkpoint_service->trackException($ex);
|
||||
|
||||
|
@ -1106,7 +1142,8 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
* @param OAuth2Request $request
|
||||
* @return OAuth2Response
|
||||
*/
|
||||
public function revoke(OAuth2Request $request = null){
|
||||
public function revoke(OAuth2Request $request = null)
|
||||
{
|
||||
|
||||
try {
|
||||
$this->last_request = $request;
|
||||
|
@ -1114,12 +1151,11 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
if (is_null($this->last_request))
|
||||
throw new InvalidOAuth2Request;
|
||||
|
||||
if(!$this->last_request->isValid())
|
||||
if (!$this->last_request->isValid())
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
|
||||
return $this->revoke_endpoint->handle($this->last_request);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
} catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->checkpoint_service->trackException($ex);
|
||||
//simple say "OK" and be on our way ...
|
||||
|
@ -1135,31 +1171,24 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
public function introspection(OAuth2Request $request = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
$this->last_request = $request;
|
||||
|
||||
if (is_null($this->last_request))
|
||||
throw new InvalidOAuth2Request;
|
||||
|
||||
if(!$this->last_request->isValid())
|
||||
if (!$this->last_request->isValid())
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
|
||||
return $this->introspection_endpoint->handle($this->last_request);
|
||||
}
|
||||
catch(ExpiredAccessTokenException $ex1)
|
||||
{
|
||||
} catch (ExpiredAccessTokenException $ex1) {
|
||||
$this->log_service->warning($ex1);
|
||||
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
||||
}
|
||||
catch(OAuth2BaseException $ex2)
|
||||
{
|
||||
} catch (OAuth2BaseException $ex2) {
|
||||
$this->log_service->warning($ex2);
|
||||
$this->checkpoint_service->trackException($ex2);
|
||||
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
} catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->checkpoint_service->trackException($ex);
|
||||
|
||||
|
@ -1183,13 +1212,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
static public function isClientAllowedToUseTokenEndpointAuth(IClient $client)
|
||||
{
|
||||
return $client->getClientType() === IClient::ClientType_Confidential ||
|
||||
$client->getApplicationType() === IClient::ApplicationType_Native;
|
||||
$client->getApplicationType() === IClient::ApplicationType_Native;
|
||||
}
|
||||
|
||||
static public function getTokenEndpointAuthMethodsPerClientType(IClient $client)
|
||||
{
|
||||
if($client->getClientType() == IClient::ClientType_Public)
|
||||
{
|
||||
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||
return ArrayUtils::convert2Assoc
|
||||
(
|
||||
array
|
||||
|
@ -1219,8 +1247,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
static public function getSigningAlgorithmsPerClientType(IClient $client)
|
||||
{
|
||||
if($client->getClientType() == IClient::ClientType_Public)
|
||||
{
|
||||
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||
return ArrayUtils::convert2Assoc
|
||||
(
|
||||
array_merge
|
||||
|
@ -1254,18 +1281,17 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
static public function getKeyManagementAlgorithmsPerClientType(IClient $client)
|
||||
{
|
||||
if($client->getClientType() == IClient::ClientType_Public)
|
||||
{
|
||||
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||
return ArrayUtils::convert2Assoc
|
||||
(
|
||||
array_diff
|
||||
(
|
||||
self::$supported_key_management_algorithms,
|
||||
array
|
||||
(
|
||||
JSONWebSignatureAndEncryptionAlgorithms::Dir
|
||||
)
|
||||
)
|
||||
array_diff
|
||||
(
|
||||
self::$supported_key_management_algorithms,
|
||||
array
|
||||
(
|
||||
JSONWebSignatureAndEncryptionAlgorithms::Dir
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return ArrayUtils::convert2Assoc
|
||||
|
@ -1281,10 +1307,9 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
public function getJWKSDocument()
|
||||
{
|
||||
$keys = $this->server_private_keys_repository->getActives();
|
||||
$set = [];
|
||||
$set = [];
|
||||
|
||||
foreach($keys as $private_key)
|
||||
{
|
||||
foreach ($keys as $private_key) {
|
||||
$jwk = RSAJWKFactory::build
|
||||
(
|
||||
new RSAJWKPEMPrivateKeySpecification
|
||||
|
@ -1419,8 +1444,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
*/
|
||||
public function endSession(OAuth2Request $request = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession");
|
||||
|
||||
$this->last_request = $request;
|
||||
|
@ -1430,59 +1454,59 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
throw new InvalidOAuth2Request;
|
||||
}
|
||||
|
||||
if(!$this->last_request->isValid()) {
|
||||
if (!$this->last_request->isValid()) {
|
||||
$this->log_service->debug_msg(sprintf("OAuth2Protocol::endSession last request is invalid error %s", $this->last_request->getLastValidationError()));
|
||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||
}
|
||||
|
||||
if(!$this->last_request instanceof OAuth2LogoutRequest) throw new InvalidOAuth2Request;
|
||||
if (!$this->last_request instanceof OAuth2LogoutRequest) throw new InvalidOAuth2Request;
|
||||
|
||||
$id_token_hint = $this->last_request->getIdTokenHint();
|
||||
$client_id = null;
|
||||
$user_id = null;
|
||||
$user = null;
|
||||
$client_id = null;
|
||||
$user_id = null;
|
||||
$user = null;
|
||||
|
||||
if(!empty($id_token_hint)){
|
||||
if (!empty($id_token_hint)) {
|
||||
$jwt = BasicJWTFactory::build($id_token_hint);
|
||||
|
||||
if((!$jwt instanceof IJWT)) {
|
||||
if ((!$jwt instanceof IJWT)) {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession invalid id_token_hint!");
|
||||
throw new InvalidOAuth2Request('invalid id_token_hint!');
|
||||
}
|
||||
|
||||
$client_id = $jwt->getClaimSet()->getAudience()->getString();
|
||||
$user_id = $jwt->getClaimSet()->getSubject();
|
||||
$user_id = $jwt->getClaimSet()->getSubject();
|
||||
}
|
||||
if(empty($client_id)){
|
||||
if (empty($client_id)) {
|
||||
$client_id = $this->last_request->getClientId();
|
||||
}
|
||||
|
||||
if(is_null($client_id)) {
|
||||
if (is_null($client_id)) {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession client_id can not be inferred.");
|
||||
throw new InvalidClientException('client_id can not be inferred.');
|
||||
}
|
||||
|
||||
$client = $this->client_repository->getClientById($client_id);
|
||||
|
||||
if(is_null($client)){
|
||||
if (is_null($client)) {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession client not found!");
|
||||
throw new InvalidClientException('Client not found!');
|
||||
}
|
||||
|
||||
$redirect_logout_uri = $this->last_request->getPostLogoutRedirectUri();
|
||||
$state = $this->last_request->getState();
|
||||
$state = $this->last_request->getState();
|
||||
|
||||
if(!empty($redirect_logout_uri) && !$client->isPostLogoutUriAllowed($redirect_logout_uri)) {
|
||||
if (!empty($redirect_logout_uri) && !$client->isPostLogoutUriAllowed($redirect_logout_uri)) {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession post_logout_redirect_uri not allowed!");
|
||||
throw new InvalidOAuth2Request('post_logout_redirect_uri not allowed!');
|
||||
}
|
||||
|
||||
if(!is_null($user_id)){
|
||||
if (!is_null($user_id)) {
|
||||
// try to get the user from id token ( if its set )
|
||||
$user_id = $this->auth_service->unwrapUserId(intval($user_id->getString()));
|
||||
$user = $this->auth_service->getUserById($user_id);
|
||||
$user = $this->auth_service->getUserById($user_id);
|
||||
|
||||
if(is_null($user)){
|
||||
if (is_null($user)) {
|
||||
$this->log_service->debug_msg("OAuth2Protocol::endSession user not found!");
|
||||
throw new InvalidOAuth2Request('user not found!');
|
||||
}
|
||||
|
@ -1490,36 +1514,29 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||
|
||||
$logged_user = $this->auth_service->getCurrentUser();
|
||||
|
||||
if(!is_null($logged_user) && !is_null($user) && $logged_user->getId() !== $user->getId()) {
|
||||
if (!is_null($logged_user) && !is_null($user) && $logged_user->getId() !== $user->getId()) {
|
||||
Log::warning(sprintf("OAuth2Protocol::endSession user does not match with current session! logged user id %s - user id %s", $logged_user->getId(), $user->getId()));
|
||||
}
|
||||
|
||||
if(!is_null($logged_user))
|
||||
if (!is_null($logged_user))
|
||||
$this->auth_service->logout();
|
||||
|
||||
if(!empty($redirect_logout_uri))
|
||||
{
|
||||
if (!empty($redirect_logout_uri)) {
|
||||
return new OAuth2LogoutResponse($redirect_logout_uri, $state);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (UriNotAllowedException $ex1)
|
||||
{
|
||||
} catch (UriNotAllowedException $ex1) {
|
||||
$this->log_service->warning($ex1);
|
||||
$this->checkpoint_service->trackException($ex1);
|
||||
|
||||
return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient);
|
||||
}
|
||||
catch(OAuth2BaseException $ex2)
|
||||
{
|
||||
} catch (OAuth2BaseException $ex2) {
|
||||
$this->log_service->warning($ex2);
|
||||
$this->checkpoint_service->trackException($ex2);
|
||||
|
||||
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
} catch (Exception $ex) {
|
||||
$this->log_service->error($ex);
|
||||
$this->checkpoint_service->trackException($ex);
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php namespace App\libs\OAuth2\Repositories;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use models\utils\IBaseRepository;
|
||||
/**
|
||||
* Interface IOAuth2OTPRepository
|
||||
* @package App\libs\OAuth2\Repositories
|
||||
*/
|
||||
interface IOAuth2OTPRepository extends IBaseRepository
|
||||
{
|
||||
/**
|
||||
* @param string $value
|
||||
* @return OAuth2OTP|null
|
||||
*/
|
||||
public function getByValue(string $value):?OAuth2OTP;
|
||||
|
||||
/**
|
||||
* @param string $connection
|
||||
* @param string $user_name
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP|null
|
||||
*/
|
||||
public function getByConnectionAndUserNameNotRedeemed
|
||||
(
|
||||
string $connection,
|
||||
string $user_name,
|
||||
?Client $client
|
||||
):?OAuth2OTP;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php namespace OAuth2\Requests;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use OAuth2\OAuth2Protocol;
|
||||
|
||||
/**
|
||||
* Class OAuth2AccessTokenRequestPasswordless
|
||||
* @package OAuth2\Requests
|
||||
*/
|
||||
final class OAuth2AccessTokenRequestPasswordless extends OAuth2TokenRequest
|
||||
{
|
||||
public static $params = [
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType => [
|
||||
OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless
|
||||
],
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => [],
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => [
|
||||
OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnectionSMS,
|
||||
] ,
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => []
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $optional_params = [
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => [
|
||||
[
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail
|
||||
]
|
||||
],
|
||||
OAuth2Protocol::OAuth2PasswordlessPhoneNumber => [
|
||||
[
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionSMS
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates current request
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
$this->last_validation_error = '';
|
||||
|
||||
// validate mandatory params
|
||||
|
||||
foreach (self::$params as $mandatory_param => $values) {
|
||||
$mandatory_val = $this->getParam($mandatory_param);
|
||||
if (empty($mandatory_val)) {
|
||||
$this->last_validation_error = sprintf("%s not set", $mandatory_param);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($values) > 0 && !in_array($mandatory_val, $values)) {
|
||||
$this->last_validation_error = sprintf("%s has not a valid value (%s)", $mandatory_param, implode(",", $values));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate optional params
|
||||
foreach (self::$optional_params as $optional_param => $rules) {
|
||||
$optional_param_val = $this->getParam($optional_param);
|
||||
if (empty($optional_param_val) && count($rules)) continue;
|
||||
foreach ($rules as $dep_param => $dep_val) {
|
||||
$dep_param_cur_val = $this->getParam($dep_param);
|
||||
if ($dep_param_cur_val != $dep_val) continue;
|
||||
if (empty($optional_param_val)) {
|
||||
$this->last_validation_error = sprintf("%s not set.", $optional_param);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getConnection(): string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessConnection);
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessEmail);
|
||||
}
|
||||
|
||||
public function getPhoneNumber(): ?string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessPhoneNumber);
|
||||
}
|
||||
|
||||
public function getUserName(): ?string
|
||||
{
|
||||
return $this->getConnection() == OAuth2Protocol::OAuth2PasswordlessConnectionEmail ? $this->getEmail() : $this->getPhoneNumber();
|
||||
}
|
||||
|
||||
public function getScopes():string{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2Protocol_Scope);
|
||||
}
|
||||
|
||||
public function getOTP():string{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2Protocol_ResponseType_OTP);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<?php namespace OAuth2\Requests;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\OAuth2Protocol;
|
||||
|
||||
/**
|
||||
* Class OAuth2PasswordlessAuthenticationRequest
|
||||
* @package OAuth2\Requests
|
||||
*/
|
||||
final class OAuth2PasswordlessAuthenticationRequest extends OAuth2AuthorizationRequest
|
||||
{
|
||||
/**
|
||||
* @param OAuth2AuthorizationRequest $auth_request
|
||||
*/
|
||||
public function __construct(OAuth2AuthorizationRequest $auth_request)
|
||||
{
|
||||
parent::__construct($auth_request->getMessage());
|
||||
}
|
||||
|
||||
public static $params = [
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => [
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP
|
||||
],
|
||||
OAuth2Protocol::OAuth2Protocol_ClientId => [],
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => [],
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::ValidOAuth2PasswordlessConnectionValues,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::ValidOAuth2PasswordlessSendValues,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $optional_params = [
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => [],
|
||||
OAuth2Protocol::OAuth2Protocol_RedirectUri => [
|
||||
[
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendLink
|
||||
]
|
||||
],
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => [
|
||||
[
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail
|
||||
]
|
||||
],
|
||||
OAuth2Protocol::OAuth2PasswordlessPhoneNumber => [
|
||||
[
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionSMS
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates current request
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
$this->last_validation_error = '';
|
||||
|
||||
// validate mandatory params
|
||||
|
||||
foreach (self::$params as $mandatory_param => $values) {
|
||||
$mandatory_val = $this->getParam($mandatory_param);
|
||||
if (empty($mandatory_val)) {
|
||||
$this->last_validation_error = sprintf("%s not set", $mandatory_param);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($values) > 0 && !in_array($mandatory_val, $values)) {
|
||||
$this->last_validation_error = sprintf("%s has not a valid value (%s)", $mandatory_param, implode(",", $values));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate optional params
|
||||
foreach (self::$optional_params as $optional_param => $rules) {
|
||||
$optional_param_val = $this->getParam($optional_param);
|
||||
if (empty($optional_param_val) && count($rules)) continue;
|
||||
foreach ($rules as $dep_param => $dep_val) {
|
||||
$dep_param_cur_val = $this->getParam($dep_param);
|
||||
if ($dep_param_cur_val != $dep_val) continue;
|
||||
if (empty($optional_param_val)) {
|
||||
$this->last_validation_error = sprintf("%s not set.", $optional_param);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getConnection(): string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessConnection);
|
||||
}
|
||||
|
||||
public function getSend(): string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessSend);
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessEmail);
|
||||
}
|
||||
|
||||
public function getNonce(): ?string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2Protocol_Nonce);
|
||||
}
|
||||
|
||||
public function getPhoneNumber(): ?string
|
||||
{
|
||||
return $this->getParam(OAuth2Protocol::OAuth2PasswordlessPhoneNumber);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php namespace OAuth2\Responses;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Utils\Http\HttpContentType;
|
||||
/**
|
||||
* Class OAuth2PasswordlessAuthenticationResponse
|
||||
* @package OAuth2\Responses
|
||||
*/
|
||||
class OAuth2PasswordlessAuthenticationResponse extends OAuth2DirectResponse
|
||||
{
|
||||
/**
|
||||
* OAuth2PasswordlessAuthenticationResponse constructor.
|
||||
* @param int $otp_length
|
||||
* @param int $otp_lifetime
|
||||
* @param string|null $scope
|
||||
*/
|
||||
public function __construct(int $otp_length, int $otp_lifetime, ?string $scope = null)
|
||||
{
|
||||
// Successful Responses: A server receiving a valid request MUST send a
|
||||
// response with an HTTP status code of 200.
|
||||
parent::__construct(self::HttpOkResponse, HttpContentType::Json);
|
||||
$this["otp_length"] = $otp_length;
|
||||
$this["otp_lifetime"] = $otp_lifetime;
|
||||
if(!empty($scope))
|
||||
$this[OAuth2Protocol::OAuth2Protocol_Scope] = $scope;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php namespace OAuth2\Services;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
/**
|
||||
* Class AccessTokenGenerator
|
||||
* @package OAuth2\Services
|
||||
*/
|
||||
final class AccessTokenGenerator extends OAuth2TokenGenerator {
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php namespace OAuth2\Services;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Class AuthorizationCodeGenerator
|
||||
* @package OAuth2\Services
|
||||
*/
|
||||
final class AuthorizationCodeGenerator extends OAuth2TokenGenerator {
|
||||
}
|
|
@ -14,16 +14,18 @@
|
|||
|
||||
use Auth\User;
|
||||
use jwt\IBasicJWT;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Exceptions\InvalidAuthorizationCodeException;
|
||||
use OAuth2\Exceptions\ReplayAttackException;
|
||||
use OAuth2\Models\AuthorizationCode;
|
||||
use OAuth2\Models\AccessToken;
|
||||
use OAuth2\Models\RefreshToken;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use OAuth2\Exceptions\InvalidAccessTokenException;
|
||||
use OAuth2\Exceptions\InvalidGrantTypeException;
|
||||
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
||||
use Utils\Model\Identifier;
|
||||
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||
use Utils\Model\AbstractIdentifier;
|
||||
|
||||
/**
|
||||
* Interface ITokenService
|
||||
|
@ -37,13 +39,13 @@ interface ITokenService {
|
|||
* Creates a brand new authorization code
|
||||
* @param OAuth2AuthorizationRequest $request
|
||||
* @param bool $has_previous_user_consent
|
||||
* @return Identifier
|
||||
* @return AbstractIdentifier
|
||||
*/
|
||||
public function createAuthorizationCode
|
||||
(
|
||||
OAuth2AuthorizationRequest $request,
|
||||
bool $has_previous_user_consent = false
|
||||
):Identifier;
|
||||
):AbstractIdentifier;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -157,7 +159,7 @@ interface ITokenService {
|
|||
* @param bool $is_hashed
|
||||
* @return bool
|
||||
*/
|
||||
public function clearAccessTokensForRefreshToken($value,$is_hashed = false);
|
||||
public function clearAccessTokensForRefreshToken($value, $is_hashed = false);
|
||||
|
||||
/**
|
||||
* Mark a given refresh token as void
|
||||
|
@ -192,4 +194,23 @@ interface ITokenService {
|
|||
AccessToken $access_token = null,
|
||||
AuthorizationCode $auth_code = null
|
||||
);
|
||||
|
||||
/**
|
||||
* @param OAuth2PasswordlessAuthenticationRequest $request
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createOTPFromRequest(OAuth2PasswordlessAuthenticationRequest $request, ?Client $client):OAuth2OTP;
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otp
|
||||
* @param Client|null $client
|
||||
* @return AccessToken
|
||||
*/
|
||||
public function createAccessTokenFromOTP
|
||||
(
|
||||
OAuth2OTP $otp,
|
||||
?Client $client
|
||||
):AccessToken;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php namespace OAuth2\Services;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
/**
|
||||
* Class RefreshTokenGenerator
|
||||
* @package OAuth2\Services
|
||||
*/
|
||||
final class RefreshTokenGenerator extends OAuth2TokenGenerator {
|
||||
}
|
|
@ -42,7 +42,7 @@ final class ClientPKCEAuthContextValidator implements IClientAuthContextValidato
|
|||
throw new InvalidClientAuthenticationContextException('client not set!');
|
||||
|
||||
if ($client->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType())
|
||||
throw new InvalidClientCredentials(sprintf('invalid token endpoint auth method %s', $context->getAuthType()));
|
||||
throw new InvalidClientCredentials(sprintf('invalid token endpoint auth method %s (%s)', $context->getAuthType(), $client->getTokenEndpointAuthInfo()->getAuthenticationMethod()));
|
||||
|
||||
if ($client->getClientType() !== IClient::ClientType_Public)
|
||||
throw new InvalidClientCredentials(sprintf('invalid client type %s', $client->getClientType()));
|
||||
|
|
|
@ -40,7 +40,7 @@ final class ClientPlainCredentialsAuthContextValidator implements IClientAuthCon
|
|||
throw new InvalidClientAuthenticationContextException('client not set!');
|
||||
|
||||
if($client->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType())
|
||||
throw new InvalidClientCredentials(sprintf('invalid token endpoint auth method %s', $context->getAuthType()));
|
||||
throw new InvalidClientCredentials(sprintf('invalid token endpoint auth method %s (%s)', $context->getAuthType(), $client->getTokenEndpointAuthInfo()->getAuthenticationMethod()));
|
||||
|
||||
if($client->getClientType() !== IClient::ClientType_Confidential)
|
||||
throw new InvalidClientCredentials(sprintf('invalid client type %s', $client->getClientType()));
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
**/
|
||||
use OpenId\Exceptions\InvalidNonce;
|
||||
use OpenId\Helpers\OpenIdErrorMessages;
|
||||
use Utils\Model\Identifier;
|
||||
use Utils\Model\AbstractIdentifier;
|
||||
use Zend\Math\Rand;
|
||||
|
||||
/**
|
||||
* Class OpenIdNonce
|
||||
* @package OpenId\Models
|
||||
*/
|
||||
final class OpenIdNonce extends Identifier
|
||||
final class OpenIdNonce extends AbstractIdentifier
|
||||
{
|
||||
const NonceRegexFormat = '/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/';
|
||||
const NonceTimeFormat = '%Y-%m-%dT%H:%M:%SZ';
|
||||
|
@ -139,7 +141,7 @@ final class OpenIdNonce extends Identifier
|
|||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
public function getType():string
|
||||
{
|
||||
return 'nonce';
|
||||
}
|
||||
|
@ -151,4 +153,27 @@ final class OpenIdNonce extends Identifier
|
|||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/*
|
||||
* MAY contain additional ASCII characters in the range 33-126 inclusive (printable non-whitespace characters), as necessary to make each response unique
|
||||
*/
|
||||
const NoncePopulation = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
/**
|
||||
* Nonce Salt Length
|
||||
*/
|
||||
const NonceSaltLength = 32;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws InvalidNonce
|
||||
*/
|
||||
public function generateValue(): string
|
||||
{
|
||||
$salt = Rand::getString(self::NonceSaltLength, self::NoncePopulation);
|
||||
$date_part = false;
|
||||
do{ $date_part = gmdate('Y-m-d\TH:i:s\Z'); } while($date_part === false);
|
||||
$raw_nonce = $date_part. $salt;
|
||||
$this->setValue($raw_nonce);
|
||||
return $this->value;
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
<?php namespace OpenId\Services;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Utils\Model\Identifier;
|
||||
use Utils\Services\UniqueIdentifierGenerator;
|
||||
use Zend\Math\Rand;
|
||||
/**
|
||||
* Class NonceUniqueIdentifierGenerator
|
||||
* @package OpenId\Services
|
||||
*/
|
||||
final class NonceUniqueIdentifierGenerator extends UniqueIdentifierGenerator {
|
||||
|
||||
/*
|
||||
* MAY contain additional ASCII characters in the range 33-126 inclusive (printable non-whitespace characters), as necessary to make each response unique
|
||||
*/
|
||||
const NoncePopulation = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
/**
|
||||
* Nonce Salt Length
|
||||
*/
|
||||
const NonceSaltLength = 32;
|
||||
|
||||
|
||||
/**
|
||||
* @param Identifier $identifier
|
||||
* @return Identifier
|
||||
*/
|
||||
protected function _generate(Identifier $identifier){
|
||||
|
||||
$salt = Rand::getString(self::NonceSaltLength, self::NoncePopulation, true);
|
||||
$date_part = false;
|
||||
do{ $date_part = gmdate('Y-m-d\TH:i:s\Z'); } while($date_part === false);
|
||||
$raw_nonce = $date_part. $salt;
|
||||
$identifier->setValue($raw_nonce);
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php namespace Utils\Model;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
/**
|
||||
* Class AbstractIdentifier
|
||||
* @package Utils\Model
|
||||
*/
|
||||
abstract class AbstractIdentifier implements Identifier
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $len
|
||||
* @param int $lifetime
|
||||
*/
|
||||
public function __construct($len, $lifetime = 0 )
|
||||
{
|
||||
$this->lifetime = $lifetime;
|
||||
$this->len = $len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $len;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $lifetime;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLength():int
|
||||
{
|
||||
return $this->len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLifetime():int
|
||||
{
|
||||
return intval($this->lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
abstract public function toArray(): array;
|
||||
}
|
|
@ -1,98 +1,41 @@
|
|||
<?php namespace Utils\Model;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
/**
|
||||
* Class Identifier
|
||||
* @package Utils\Model
|
||||
*/
|
||||
abstract class Identifier
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
interface Identifier
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $len
|
||||
* @param int $lifetime
|
||||
*/
|
||||
public function __construct($len, $lifetime = 0 )
|
||||
{
|
||||
$this->lifetime = $lifetime;
|
||||
$this->len = $len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $len;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $lifetime;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @param IdentifierGenerator $generator
|
||||
* @return $this
|
||||
*/
|
||||
public function generate(IdentifierGenerator $generator)
|
||||
{
|
||||
return $generator->generate($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLenght()
|
||||
{
|
||||
return $this->len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLifetime()
|
||||
{
|
||||
return intval($this->lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
public function getLength():int;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
public function setValue(string $value);
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLifetime():int;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getType();
|
||||
public function getType():string;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
abstract public function toArray(): array;
|
||||
public function generateValue():string;
|
||||
}
|
|
@ -22,5 +22,5 @@ interface IdentifierGenerator {
|
|||
* @param Identifier $identifier
|
||||
* @return Identifier
|
||||
*/
|
||||
public function generate(Identifier $identifier);
|
||||
public function generate(Identifier $identifier):Identifier;
|
||||
}
|
|
@ -17,7 +17,7 @@ use Zend\Crypt\Hash;
|
|||
* Class UniqueIdentifierGenerator
|
||||
* @package Utils\Services
|
||||
*/
|
||||
abstract class UniqueIdentifierGenerator implements IdentifierGenerator
|
||||
class UniqueIdentifierGenerator implements IdentifierGenerator
|
||||
{
|
||||
|
||||
/**
|
||||
|
@ -37,20 +37,13 @@ abstract class UniqueIdentifierGenerator implements IdentifierGenerator
|
|||
* @param Identifier $identifier
|
||||
* @return Identifier
|
||||
*/
|
||||
public function generate(Identifier $identifier){
|
||||
|
||||
public function generate(Identifier $identifier):Identifier{
|
||||
do
|
||||
{
|
||||
$key = sprintf("%s.%s", $identifier->getType(), Hash::compute('sha256', $this->_generate($identifier)->getValue()));
|
||||
$key = sprintf("%s.%s", $identifier->getType(), Hash::compute('sha256', $identifier->generateValue()));
|
||||
}
|
||||
while(!$this->cache_service->addSingleValue($key, $key));
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Identifier $identifier
|
||||
* @return Identifier
|
||||
*/
|
||||
abstract protected function _generate(Identifier $identifier);
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
|
||||
return [
|
||||
"lifetime" => env("OTP_DEFAULT_LIFETIME", 120),
|
||||
"length" => env("OTP_DEFAULT_LENGTH", 6)
|
||||
];
|
|
@ -0,0 +1,94 @@
|
|||
<?php namespace Database\Migrations;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema as Schema;
|
||||
use LaravelDoctrine\Migrations\Schema\Builder;
|
||||
use LaravelDoctrine\Migrations\Schema\Table;
|
||||
/**
|
||||
* Class Version20210616123839
|
||||
* @package Database\Migrations
|
||||
*/
|
||||
class Version20210616123839 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$builder = new Builder($schema);
|
||||
|
||||
if (!$builder->hasTable("oauth2_otp")) {
|
||||
$builder->create("oauth2_otp", function (Table $table) {
|
||||
$table->bigInteger("id", true, false);
|
||||
$table->primary("id");
|
||||
$table->timestamps();
|
||||
$table->string("value")->setLength(50)->setNotnull(true);
|
||||
$table->string("connection")->setLength(10)->setNotnull(true);//sms|mail
|
||||
$table->string("send")->setLength(10)->setNotnull(true);//code|link
|
||||
$table->string("scopes")->setNotnull(true);
|
||||
$table->string("email")->setLength(50)->setNotnull(false);
|
||||
$table->string("phone_number")->setLength(50)->setNotnull(false);
|
||||
$table->string("nonce")->setLength(50)->setNotnull(false);
|
||||
$table->integer("redeemed_attempts")->setNotnull(true)->setDefault(0);
|
||||
$table->string("redeemed_from_ip")->setNotnull(false);
|
||||
$table->string("redirect_url")->setLength(255)->setNotnull(false);
|
||||
$table->integer('length')->setNotnull(false)->setDefault(6);
|
||||
// seconds
|
||||
$table->integer('lifetime')->setNotnull(true);
|
||||
$table->dateTime('redeemed_at')->setNotnull(false);
|
||||
// FK Optional
|
||||
$table->bigInteger("oauth2_client_id", false, false)->setNotnull(false)->setDefault('NULL');
|
||||
$table->index("oauth2_client_id", "oauth2_client_id");
|
||||
$table->foreign("oauth2_client", "oauth2_client_id", "id", ["onDelete" => "CASCADE"]);
|
||||
|
||||
$table->unique(["oauth2_client_id", "value"]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
if ($builder->hasTable("oauth2_client") && !$builder->hasColumn("oauth2_client","otp_enabled")) {
|
||||
$builder->table('oauth2_client', function (Table $table) {
|
||||
//
|
||||
$table->boolean('otp_enabled')->setNotnull(false)->setDefault(0);
|
||||
// characters
|
||||
$table->integer('otp_length')->setNotnull(false)->setDefault(6);
|
||||
// seconds
|
||||
$table->integer('otp_lifetime')->setNotnull(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$builder = new Builder($schema);
|
||||
|
||||
if ($builder->hasTable("oauth2_otp")) {
|
||||
$builder->drop("oauth2_otp");
|
||||
}
|
||||
|
||||
if ($builder->hasTable("oauth2_client") && $builder->hasColumn("oauth2_client","otp_enabled")) {
|
||||
$builder->table('oauth2_client', function (Table $table) {
|
||||
//
|
||||
$table->dropColumn('otp_enabled');
|
||||
// characters
|
||||
$table->dropColumn('otp_length');
|
||||
// seconds
|
||||
$table->dropColumn('otp_lifetime');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php namespace Database\Migrations;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema as Schema;
|
||||
/**
|
||||
* Class Version20210616123841
|
||||
* @package Database\Migrations
|
||||
*/
|
||||
class Version20210616123841 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$sql = <<<SQL
|
||||
ALTER TABLE oauth2_otp MODIFY `connection`
|
||||
enum(
|
||||
'sms',
|
||||
'email'
|
||||
) default 'email' null;
|
||||
SQL;
|
||||
$this->addSql($sql);
|
||||
|
||||
$sql = <<<SQL
|
||||
ALTER TABLE oauth2_otp MODIFY send
|
||||
enum(
|
||||
'code',
|
||||
'link'
|
||||
) default 'code' null;
|
||||
SQL;
|
||||
$this->addSql($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -33,6 +33,10 @@ chmod 777 vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serial
|
|||
|
||||
Laravel may require some permissions to be configured: folders within storage and vendor require write access by the web server.
|
||||
|
||||
## validate schema
|
||||
|
||||
php artisan doctrine:schema:validate
|
||||
|
||||
## create schema
|
||||
|
||||
php artisan doctrine:schema:create --sql --em=model > model.sql
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>Dear User</p>
|
||||
<p>Here is your Verification code <b>{{$otp}}</b>.</p>
|
||||
<p>Should be valid for {{$lifetime}} minutes.</p>
|
||||
<br/>
|
||||
<br/>
|
||||
<p>Cheers,<br/>Your {!! Config::get('app.tenant_name') !!} Support Team</p>
|
||||
</body>
|
||||
</html><?php
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -14,8 +14,8 @@
|
|||
use Models\OAuth2\ApiEndpoint;
|
||||
use Models\OAuth2\Api;
|
||||
use Models\OAuth2\ApiScope;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
/**
|
||||
* Class ApiEndpointTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -13,8 +13,8 @@
|
|||
**/
|
||||
use Models\OAuth2\ApiScope;
|
||||
use Models\OAuth2\Api;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
/**
|
||||
* Class ApiScopeTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -13,8 +13,8 @@
|
|||
**/
|
||||
use Models\OAuth2\Api;
|
||||
use Models\OAuth2\ResourceServer;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
/**
|
||||
* Class ApiTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -16,7 +16,7 @@ use OpenId\Helpers\AssociationFactory;
|
|||
use OpenId\OpenIdProtocol;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
use Utils\Exceptions\UnacquiredLockException;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use Mockery;
|
||||
/**
|
||||
* Class AssociationServiceTest
|
||||
*/
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -20,7 +20,7 @@ use LaravelDoctrine\ORM\Facades\EntityManager;
|
|||
/**
|
||||
* Class ClientApiTest
|
||||
*/
|
||||
class ClientApiTest extends \Tests\BrowserKitTestCase {
|
||||
class ClientApiTest extends BrowserKitTestCase {
|
||||
|
||||
private $current_realm;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -16,8 +16,9 @@ use jwk\JSONWebKeyPublicKeyUseValues;
|
|||
use Models\OAuth2\Client;
|
||||
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use TestKeys;
|
||||
/**
|
||||
* Class ClientPublicKeyApiTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -16,7 +16,6 @@ use Utils\Services\UtilsServiceCatalog;
|
|||
use OpenId\Services\OpenIdServiceCatalog;
|
||||
use Auth\Repositories\IUserRepository;
|
||||
use Auth\IAuthenticationExtensionService;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\App;
|
||||
/**
|
||||
* Class CustomAuthProviderTest
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -15,7 +15,6 @@ use OpenId\Helpers\AssocHandleGenerator;
|
|||
use OpenId\Helpers\OpenIdCryptoHelper;
|
||||
use OpenId\Requests\OpenIdDHAssociationSessionRequest;
|
||||
use Zend\Crypt\PublicKey\DiffieHellman;
|
||||
use Tests\TestCase;
|
||||
/**
|
||||
* Class DiffieHellmanTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -11,7 +11,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Tests\BrowserKitTestCase;
|
||||
|
||||
/***
|
||||
* Class DiscoveryControllerTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -17,7 +17,6 @@ use Utils\Services\IAuthService;
|
|||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
|
||||
/**
|
||||
* Class OAuth2ProtectedApiTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -12,13 +12,13 @@
|
|||
* limitations under the License.
|
||||
**/
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Utils\Services\IAuthService;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
/**
|
||||
* Class OAuth2ProtocolTest
|
||||
* Test Suite for OAuth2 Protocol
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use OAuth2ProtectedApiTest;
|
||||
use App\libs\OAuth2\IUserScopes;
|
||||
/**
|
||||
* Class OAuth2UserRegistrationServiceApiTest
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2020 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -13,7 +13,7 @@
|
|||
**/
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use LaravelDoctrine\ORM\Facades\Registry;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\SSO\DisqusSSOProfile;
|
||||
use App\Models\Utils\BaseEntity;
|
||||
|
|
|
@ -0,0 +1,557 @@
|
|||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use App\Mail\OAuth2PasswordlessOTPMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use jwe\IJWE;
|
||||
use jwk\impl\RSAJWKFactory;
|
||||
use jwk\JSONWebKeyPublicKeyUseValues;
|
||||
use jws\IJWS;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use utils\factories\BasicJWTFactory;
|
||||
use Utils\Services\UtilsServiceCatalog;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
/**
|
||||
* Class OIDCPasswordlessTest
|
||||
* @package Tests
|
||||
*/
|
||||
class OIDCPasswordlessTest extends OpenStackIDBaseTest
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
public static $client = null;
|
||||
|
||||
protected function setUp():void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$client_repository = EntityManager::getRepository(Client::class);
|
||||
|
||||
$clients = $client_repository->findAll();
|
||||
|
||||
|
||||
self::$client = $clients[0];
|
||||
|
||||
self::$client->enablePasswordless();
|
||||
self::$client->setOtpLifetime(60 * 3);
|
||||
self::$client->setOtpLength(6);
|
||||
self::$client->setTokenEndpointAuthMethod(OAuth2Protocol::TokenEndpoint_AuthMethod_ClientSecretBasic);
|
||||
EntityManager::persist(self::$client);
|
||||
}
|
||||
|
||||
protected function tearDown():void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $current_realm;
|
||||
|
||||
protected function prepareForTests()
|
||||
{
|
||||
parent::prepareForTests();
|
||||
App::singleton(UtilsServiceCatalog::ServerConfigurationService, StubServerConfigurationService::class);
|
||||
$this->current_realm = Config::get('app.url');
|
||||
Session::start();
|
||||
}
|
||||
|
||||
public function testCodeEmailFlowErrorScopes(){
|
||||
$scope = sprintf('%s profile email',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp){
|
||||
$otp = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
// ask for wider scopes
|
||||
$scope = sprintf('%s profile email address',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp,
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(400);
|
||||
$content = $response->getContent();
|
||||
$response = json_decode($content);
|
||||
$this->assertTrue(!empty($response->error));
|
||||
}
|
||||
|
||||
public function testCodeEmailFlowError(){
|
||||
$scope = sprintf('%s profile email',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendLink,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(400);
|
||||
}
|
||||
|
||||
public function testCodeEmailFlowNoRefreshToken(){
|
||||
$scope = sprintf('%s profile email address',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp){
|
||||
$otp = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp,
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$response = json_decode($content);
|
||||
$access_token = $response->access_token;
|
||||
$id_token = $response->id_token;
|
||||
|
||||
$this->assertTrue(!empty($access_token));
|
||||
$this->assertTrue(!property_exists($response, "refresh_token"));
|
||||
$this->assertTrue(!empty($id_token));
|
||||
|
||||
}
|
||||
|
||||
public function testCodeEmailFlowConsecutiveOTP(){
|
||||
$scope = sprintf('%s profile email address',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp1 = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp1){
|
||||
$otp1 = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp2 = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp2){
|
||||
$otp2 = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
$repository = EntityManager::getRepository(OAuth2OTP::class);
|
||||
|
||||
$otp1 = $repository->getByValue($otp1);
|
||||
$this->assertTrue(is_null($otp1));
|
||||
$otp2 = $repository->getByValue($otp2);
|
||||
$this->assertTrue(!is_null($otp2));
|
||||
}
|
||||
|
||||
public function testCodeEmailFlowNarrowScopes(){
|
||||
$scope = sprintf('%s profile email address',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp){
|
||||
$otp = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
// ask for wider scopes
|
||||
$scope = sprintf('%s profile email',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp,
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$content = $response->getContent();
|
||||
$response = json_decode($content);
|
||||
$this->assertTrue(!empty($response->id_token));
|
||||
}
|
||||
|
||||
public function testCodeEmailFlow() {
|
||||
|
||||
$scope = sprintf('%s profile email address %s',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
OAuth2Protocol::OfflineAccess_Scope
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp){
|
||||
$otp = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp,
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$response = json_decode($content);
|
||||
$access_token = $response->access_token;
|
||||
$refresh_token = $response->refresh_token;
|
||||
$id_token = $response->id_token;
|
||||
|
||||
$this->assertTrue(!empty($access_token));
|
||||
$this->assertTrue(!empty($refresh_token));
|
||||
$this->assertTrue(!empty($id_token));
|
||||
|
||||
$jwt = BasicJWTFactory::build($id_token);
|
||||
$use_enc = false;
|
||||
if ($use_enc) {
|
||||
$this->assertTrue($jwt instanceof IJWE);
|
||||
|
||||
$recipient_key = RSAJWKFactory::build
|
||||
(
|
||||
new RSAJWKPEMPrivateKeySpecification
|
||||
(
|
||||
TestSeeder::$client_private_key_1,
|
||||
RSAJWKPEMPrivateKeySpecification::WithoutPassword,
|
||||
$jwt->getJOSEHeader()->getAlgorithm()->getString()
|
||||
)
|
||||
);
|
||||
|
||||
$recipient_key->setKeyUse(JSONWebKeyPublicKeyUseValues::Encryption)->setId('recipient_public_key');
|
||||
|
||||
$jwt->setRecipientKey($recipient_key);
|
||||
|
||||
$payload = $jwt->getPlainText();
|
||||
|
||||
$jwt = BasicJWTFactory::build($payload);
|
||||
|
||||
$this->assertTrue($jwt instanceof IJWS);
|
||||
}
|
||||
|
||||
return $access_token;
|
||||
}
|
||||
|
||||
public function testInvalidRedeemCodeEmailFlow() {
|
||||
|
||||
$scope = sprintf('%s profile email address %s',
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
OAuth2Protocol::OfflineAccess_Scope
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => self::$client->getClientId(),
|
||||
'scope' => $scope,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => '123456',
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[]);
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
$otp = null;
|
||||
Mail::assertNotQueued(OAuth2PasswordlessOTPMail::class, function(OAuth2PasswordlessOTPMail $email) use(&$otp){
|
||||
$otp = $email->otp;
|
||||
});
|
||||
|
||||
$this->assertEquals('application/json;charset=UTF-8', $response->headers->get('Content-Type'));
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$otp_response = json_decode($content);
|
||||
|
||||
$this->assertTrue($otp_response->scope == $scope);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp.'1',
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(400);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp.'2',
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(400);
|
||||
|
||||
// exchange
|
||||
$params = [
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail =>"test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType_OTP => $otp.'3',
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => $scope
|
||||
];
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode(self::$client->getClientId() . ':' . self::$client->getClientSecret())));
|
||||
|
||||
$this->assertResponseStatus(400);
|
||||
|
||||
$repository = EntityManager::getRepository(OAuth2OTP::class);
|
||||
|
||||
$otp = $repository->getByValue($otp);
|
||||
$this->assertTrue(!is_null($otp));
|
||||
$this->assertTrue(!$otp->isValid());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -35,6 +35,8 @@ use jwt\impl\UnsecuredJWT;
|
|||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Database\Seeders\TestSeeder;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
/**
|
||||
* Class OIDCProtocolTest
|
||||
* http://openid.net/wordpress-content/uploads/2015/02/OpenID-Connect-Conformance-Profiles.pdf
|
||||
|
@ -49,7 +51,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||
protected function prepareForTests()
|
||||
{
|
||||
parent::prepareForTests();
|
||||
App::singleton(UtilsServiceCatalog::ServerConfigurationService, 'StubServerConfigurationService');
|
||||
App::singleton(UtilsServiceCatalog::ServerConfigurationService, StubServerConfigurationService::class);
|
||||
$this->current_realm = Config::get('app.url');
|
||||
Session::start();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2021 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\Models\OAuth2\Factories\OTPFactory;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Models\OAuth2\Client;
|
||||
use OAuth2\Factories\OAuth2AuthorizationRequestFactory;
|
||||
use OAuth2\OAuth2Message;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use Utils\Services\IdentifierGenerator;
|
||||
|
||||
/**
|
||||
* Class OTPModelTest
|
||||
* @package Tests
|
||||
*/
|
||||
class OTPModelTest extends BrowserKitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
static $aauth2_client;
|
||||
|
||||
protected function setUp():void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
protected function tearDown():void
|
||||
{
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testCreateFromRequest(){
|
||||
|
||||
$client_repository = EntityManager::getRepository(Client::class);
|
||||
|
||||
$clients = $client_repository->findAll();
|
||||
|
||||
$this->assertTrue(count($clients) > 0);
|
||||
|
||||
$client = $clients[0];
|
||||
|
||||
if(!$client instanceof Client) return;
|
||||
|
||||
$client->enablePasswordless();
|
||||
$client->setOtpLifetime(60 * 3);
|
||||
$client->setOtpLength(6);
|
||||
|
||||
EntityManager::persist($client);
|
||||
$values =
|
||||
[
|
||||
OAuth2Protocol::OAuth2Protocol_ClientId => $client->getClientId(),
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => "test_scope"
|
||||
];
|
||||
|
||||
$request = OAuth2AuthorizationRequestFactory::getInstance()->build
|
||||
(
|
||||
new OAuth2Message($values)
|
||||
);
|
||||
|
||||
$this->assertTrue($request->isValid());
|
||||
$otp = OTPFactory::buildFromRequest($request, App::make(IdentifierGenerator::class), $client);
|
||||
EntityManager::persist($client);
|
||||
EntityManager::flush();
|
||||
$this->assertTrue($client->getOTPGrantsByEmailNotRedeemed("test@test.com")->count() > 0);
|
||||
$this->assertTrue(strlen($otp->getValue()) == $client->getOtpLength());
|
||||
}
|
||||
|
||||
public function testCreateFromPayloadNoClient(){
|
||||
$payload =
|
||||
[
|
||||
OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_OTP,
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => OAuth2Protocol::OAuth2PasswordlessConnectionEmail,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => OAuth2Protocol::OAuth2PasswordlessSendCode,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => "test@test.com",
|
||||
OAuth2Protocol::OAuth2Protocol_Scope => "test_scope"
|
||||
];
|
||||
|
||||
$otp = OTPFactory::buildFromPayload($payload, App::make(IdentifierGenerator::class));
|
||||
|
||||
EntityManager::persist($otp);
|
||||
EntityManager::flush();
|
||||
$this->assertTrue($otp->getId() > 0);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -23,6 +23,7 @@ use Illuminate\Support\Facades\Config;
|
|||
use Models\OpenId\OpenIdTrustedSite;
|
||||
use OpenId\Extensions\Implementations\OpenIdSREGExtension_1_0;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
/**
|
||||
* Class OpenIdProtocolTest
|
||||
* Test Suite for OpenId Protocol
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 Openstack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -13,7 +13,6 @@
|
|||
**/
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\BrowserKitTestCase;
|
||||
/**
|
||||
* Class OpenStackIDBaseTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -15,7 +15,6 @@ use Models\OAuth2\ResourceServer;
|
|||
use Illuminate\Support\Facades\Config;
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
/**
|
||||
* Class ResourceServerApiTest
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -14,10 +14,9 @@
|
|||
use OpenId\Services\OpenIdServiceCatalog;
|
||||
use Utils\Services\IAuthService;
|
||||
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
||||
use OpenId\Models\IOpenIdUser;
|
||||
use Auth\User;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Mockery;
|
||||
/**
|
||||
* Class TrustedSitesServiceTest
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2020 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -11,7 +11,6 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Tests\TestCase;
|
||||
use App\Http\Utils\CookieSameSitePolicy;
|
||||
/**
|
||||
* Class UserAgentTests
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -12,7 +12,6 @@
|
|||
* limitations under the License.
|
||||
**/
|
||||
use Auth\UserNameGeneratorService;
|
||||
use Tests\BrowserKitTestCase;
|
||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||
use Auth\User;
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
<?php namespace Tests;
|
||||
/**
|
||||
* Copyright 2015 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -13,7 +13,6 @@
|
|||
**/
|
||||
use OpenId\Xrds\XRDSDocumentBuilder;
|
||||
use OpenId\Xrds\XRDSService;
|
||||
use Tests\BrowserKitTestCase;
|
||||
/**
|
||||
* Class XRDSDocumentTest
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue