Passwordlress Flow
* API endpoints for embedded login flow * unit tests * UI Tweaks * Universal login implementation 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
87f8920dd5
commit
84fb6f6299
@ -385,7 +385,7 @@ class AdminController extends Controller {
|
||||
if(is_null($endpoint)) return Response::view('errors.404', [], 404);
|
||||
$user = $this->auth_service->getCurrentUser();
|
||||
$selected_scopes = [];
|
||||
$list = $endpoint->getScopes();
|
||||
$list = $endpoint->getScope();
|
||||
foreach($list as $selected_scope){
|
||||
$selected_scopes[] = $selected_scope->getId();
|
||||
}
|
||||
|
@ -675,6 +675,10 @@ final class ClientApiController extends APICRUDController
|
||||
'id_token_encrypted_response_alg' => 'sometimes|required|encrypted_alg',
|
||||
'id_token_encrypted_response_enc' => 'sometimes|required|encrypted_enc',
|
||||
'admin_users' => 'nullable|int_array',
|
||||
'pkce_enabled' => 'sometimes|boolean',
|
||||
'otp_enabled' => 'sometimes|boolean',
|
||||
'otp_length' => 'sometimes|integer|min:4|max:8',
|
||||
'otp_lifetime' => 'sometimes|integer|min:60|max:600',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -101,24 +101,32 @@ final class OAuth2ProviderController extends Controller
|
||||
|
||||
return $response;
|
||||
} catch (OAuth2BaseException $ex1) {
|
||||
$payload = [
|
||||
'error' => $ex1->getError(),
|
||||
'error_description' => $ex1->getMessage()
|
||||
];
|
||||
if (request()->isJson()) {
|
||||
return Response::json($payload, 400);
|
||||
}
|
||||
return Response::view
|
||||
(
|
||||
'errors.400',
|
||||
[
|
||||
'error' => $ex1->getError(),
|
||||
'error_description' => $ex1->getMessage()
|
||||
],
|
||||
$payload,
|
||||
400
|
||||
);
|
||||
} catch (Exception $ex) {
|
||||
Log::error($ex);
|
||||
$payload = [
|
||||
'error' => "Bad Request",
|
||||
'error_description' => "Generic Error"
|
||||
];
|
||||
if (request()->isJson()) {
|
||||
return Response::json($payload, 400);
|
||||
}
|
||||
return Response::view
|
||||
(
|
||||
'errors.400',
|
||||
[
|
||||
'error' => "Bad Request",
|
||||
'error_description' => "Generic Error"
|
||||
],
|
||||
$payload,
|
||||
400
|
||||
);
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use models\exceptions\EntityNotFoundException;
|
||||
use models\exceptions\ValidationException;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Factories\OAuth2AuthorizationRequestFactory;
|
||||
use OAuth2\OAuth2Message;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
use OAuth2\Repositories\IApiScopeRepository;
|
||||
use OAuth2\Repositories\IClientRepository;
|
||||
use OpenId\Services\IUserService;
|
||||
@ -170,7 +174,9 @@ final class UserController extends OpenIdController
|
||||
$this->security_context_service = $security_context_service;
|
||||
|
||||
$this->middleware(function ($request, $next) {
|
||||
Log::debug(sprintf("UserController::middleware"));
|
||||
|
||||
Log::debug(sprintf("UserController::middleware route %s %s", $request->getMethod(), $request->getRequestUri()));
|
||||
|
||||
if ($this->openid_memento_service->exists()) {
|
||||
//openid stuff
|
||||
Log::debug(sprintf("UserController::middleware OIDC"));
|
||||
@ -232,16 +238,17 @@ final class UserController extends OpenIdController
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function getAccount(){
|
||||
public function getAccount()
|
||||
{
|
||||
try {
|
||||
$email = Request::input("email", "");
|
||||
if(empty($email)){
|
||||
if (empty($email)) {
|
||||
throw new ValidationException("empty email.");
|
||||
}
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($email);
|
||||
|
||||
if(is_null($user))
|
||||
if (is_null($user))
|
||||
throw new EntityNotFoundException();
|
||||
|
||||
return $this->ok(
|
||||
@ -250,16 +257,83 @@ final class UserController extends OpenIdController
|
||||
'full_name' => $user->getFullName()
|
||||
]
|
||||
);
|
||||
}
|
||||
catch (ValidationException $ex){
|
||||
} catch (ValidationException $ex) {
|
||||
Log::warning($ex);
|
||||
return $this->error412($ex->getMessages());
|
||||
}
|
||||
catch (EntityNotFoundException $ex){
|
||||
} catch (EntityNotFoundException $ex) {
|
||||
Log::warning($ex);
|
||||
return $this->error404();
|
||||
} catch (Exception $ex) {
|
||||
Log::error($ex);
|
||||
return $this->error500($ex);
|
||||
}
|
||||
catch (Exception $ex){
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function emitOTP()
|
||||
{
|
||||
try {
|
||||
|
||||
$username = Request::input("username", "");
|
||||
$connection = Request::input("connection", "");
|
||||
$send = Request::input("send", "");
|
||||
|
||||
if (empty($username)) {
|
||||
throw new ValidationException("empty username.");
|
||||
}
|
||||
|
||||
if (empty($connection)) {
|
||||
throw new ValidationException("empty username.");
|
||||
}
|
||||
|
||||
if (empty($send)) {
|
||||
throw new ValidationException("empty username.");
|
||||
}
|
||||
|
||||
$client = null;
|
||||
|
||||
// check if we have a former oauth2 request
|
||||
if ($this->oauth2_memento_service->exists()) {
|
||||
|
||||
Log::debug("UserController::getOTP exist a oauth auth request on session");
|
||||
|
||||
$oauth_auth_request = OAuth2AuthorizationRequestFactory::getInstance()->build
|
||||
(
|
||||
OAuth2Message::buildFromMemento($this->oauth2_memento_service->load())
|
||||
);
|
||||
|
||||
if ($oauth_auth_request->isValid()) {
|
||||
|
||||
$client_id = $oauth_auth_request->getClientId();
|
||||
|
||||
$client = $this->client_repository->getClientById($client_id);
|
||||
if (is_null($client))
|
||||
throw new ValidationException("client does not exists");
|
||||
|
||||
$this->oauth2_memento_service->serialize($oauth_auth_request->getMessage()->createMemento());
|
||||
}
|
||||
}
|
||||
|
||||
$otp = $this->token_service->createOTPFromPayload([
|
||||
OAuth2Protocol::OAuth2PasswordlessConnection => $connection,
|
||||
OAuth2Protocol::OAuth2PasswordlessSend => $send,
|
||||
OAuth2Protocol::OAuth2PasswordlessEmail => ($connection == OAuth2Protocol::OAuth2PasswordlessConnectionEmail) ? $username : null,
|
||||
OAuth2Protocol::OAuth2PasswordlessPhoneNumber => ($connection == OAuth2Protocol::OAuth2PasswordlessConnectionSMS) ? $username : null
|
||||
], $client);
|
||||
|
||||
return $this->created([
|
||||
'otp_length' => $otp->getLength(),
|
||||
'otp_lifetime' => $otp->getLifetime(),
|
||||
]);
|
||||
} catch (ValidationException $ex) {
|
||||
Log::warning($ex);
|
||||
return $this->error412($ex->getMessages());
|
||||
} catch (EntityNotFoundException $ex) {
|
||||
Log::warning($ex);
|
||||
return $this->error404();
|
||||
} catch (Exception $ex) {
|
||||
Log::error($ex);
|
||||
return $this->error500($ex);
|
||||
}
|
||||
@ -271,6 +345,7 @@ final class UserController extends OpenIdController
|
||||
$login_attempts = 0;
|
||||
$username = '';
|
||||
$user = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -279,7 +354,7 @@ final class UserController extends OpenIdController
|
||||
if (isset($data['username']))
|
||||
$data['username'] = trim($data['username']);
|
||||
|
||||
if(isset($data['password']))
|
||||
if (isset($data['password']))
|
||||
$data['password'] = trim($data['password']);
|
||||
|
||||
$login_attempts = intval(Request::input('login_attempts'));
|
||||
@ -287,52 +362,90 @@ final class UserController extends OpenIdController
|
||||
$rules = [
|
||||
'username' => 'required|email',
|
||||
'password' => 'required',
|
||||
'flow' => 'required|in:otp,password',
|
||||
'connection' => 'sometimes|string|in:sms,email',
|
||||
];
|
||||
|
||||
if ($login_attempts >= $max_login_attempts_2_show_captcha)
|
||||
{
|
||||
if ($login_attempts >= $max_login_attempts_2_show_captcha) {
|
||||
$rules['g-recaptcha-response'] = 'required|recaptcha';
|
||||
}
|
||||
// Create a new validator instance.
|
||||
$validator = Validator::make($data, $rules);
|
||||
|
||||
if ($validator->passes()) {
|
||||
|
||||
$username = $data['username'];
|
||||
$password = $data['password'];
|
||||
$flow = $data['flow'];
|
||||
$remember = Request::input("remember");
|
||||
$remember = !is_null($remember);
|
||||
$connection = $data['connection'] ?? null;
|
||||
|
||||
if ($this->auth_service->login($username, $password, $remember))
|
||||
{
|
||||
return $this->login_strategy->postLogin();
|
||||
try {
|
||||
if ($flow == "password" && $this->auth_service->login($username, $password, $remember)) {
|
||||
return $this->login_strategy->postLogin();
|
||||
}
|
||||
|
||||
if ($flow == "otp") {
|
||||
|
||||
$client = null;
|
||||
|
||||
// check if we have a former oauth2 request
|
||||
if ($this->oauth2_memento_service->exists()) {
|
||||
|
||||
Log::debug("UserController::getOTP exist a oauth auth request on session");
|
||||
|
||||
$oauth_auth_request = OAuth2AuthorizationRequestFactory::getInstance()->build
|
||||
(
|
||||
OAuth2Message::buildFromMemento($this->oauth2_memento_service->load())
|
||||
);
|
||||
|
||||
if ($oauth_auth_request->isValid()) {
|
||||
|
||||
$client_id = $oauth_auth_request->getClientId();
|
||||
|
||||
$client = $this->client_repository->getClientById($client_id);
|
||||
if (is_null($client))
|
||||
throw new ValidationException("client does not exists");
|
||||
|
||||
$this->oauth2_memento_service->serialize($oauth_auth_request->getMessage()->createMemento());
|
||||
}
|
||||
}
|
||||
|
||||
$otpClaim = OAuth2OTP::fromParams($username, $connection, $password);
|
||||
$this->auth_service->loginWithOTP($otpClaim, $client);
|
||||
return $this->login_strategy->postLogin();
|
||||
}
|
||||
} catch (AuthenticationException $ex) {
|
||||
// failed login attempt...
|
||||
|
||||
$user = $this->auth_service->getUserByUsername($username);
|
||||
if (!is_null($user)) {
|
||||
$login_attempts = $user->getLoginFailedAttempt();
|
||||
}
|
||||
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
[
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'error_message' => $ex->getMessage(),
|
||||
'user_fullname' => !is_null($user) ? $user->getFullName() : "",
|
||||
'user_pic' => !is_null($user) ? $user->getPic(): "",
|
||||
'user_verified' => true,
|
||||
'username' => $username,
|
||||
'flow' => $flow
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
//failed login attempt...
|
||||
$user = $this->auth_service->getUserByUsername($username);
|
||||
|
||||
if (!is_null($user)) {
|
||||
$login_attempts = $user->getLoginFailedAttempt();
|
||||
}
|
||||
|
||||
return $this->login_strategy->errorLogin
|
||||
(
|
||||
[
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'error_message' => "We are sorry, your username or password does not match an existing record.",
|
||||
'user_fullname' => $user->getFullName(),
|
||||
'user_pic' => $user->getPic(),
|
||||
'user_verified' => true,
|
||||
'username' => $username,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// validator errors
|
||||
$response_data = [
|
||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||
'login_attempts' => $login_attempts,
|
||||
'validator' => $validator
|
||||
'validator' => $validator,
|
||||
];
|
||||
|
||||
if(!is_null($user)){
|
||||
@ -376,9 +489,14 @@ final class UserController extends OpenIdController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\Response|mixed
|
||||
*/
|
||||
public function getConsent()
|
||||
{
|
||||
if (is_null($this->consent_strategy)) {
|
||||
|
||||
Log::error(sprintf("UserController::getConsent consent strategy is null. request %s %s", Request::method(), Request::path()));
|
||||
return Response::view
|
||||
(
|
||||
'errors.400',
|
||||
@ -389,10 +507,12 @@ final class UserController extends OpenIdController
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return $this->consent_strategy->getConsent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|mixed
|
||||
*/
|
||||
public function postConsent()
|
||||
{
|
||||
try {
|
||||
@ -405,7 +525,9 @@ final class UserController extends OpenIdController
|
||||
$validator = Validator::make($data, $rules);
|
||||
if ($validator->passes()) {
|
||||
if (is_null($this->consent_strategy)) {
|
||||
Log::warning(sprintf("UserController::postConsent consent strategy is null"));
|
||||
|
||||
Log::error(sprintf("UserController::postConsent consent strategy is null. request %s %s", Request::method(), Request::path()));
|
||||
|
||||
return Response::view
|
||||
(
|
||||
'errors.400',
|
||||
@ -463,7 +585,6 @@ final class UserController extends OpenIdController
|
||||
$pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url;
|
||||
|
||||
$params = [
|
||||
|
||||
'show_fullname' => $user->getShowProfileFullName(),
|
||||
'username' => $user->getFullName(),
|
||||
'show_email' => $user->getShowProfileEmail(),
|
||||
|
@ -67,7 +67,7 @@ class Kernel extends HttpKernel
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'ssl' => \App\Http\Middleware\SSLMiddleware::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
|
||||
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
|
||||
'oauth2.endpoint' => \App\Http\Middleware\OAuth2BearerAccessTokenRequestValidator::class,
|
||||
'oauth2.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdmin::class,
|
||||
|
76
app/Mail/OAuth2PasswordlessOTPMail.php
Normal file
76
app/Mail/OAuth2PasswordlessOTPMail.php
Normal file
@ -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 = 3;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $otp;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* @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->email = $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->email));
|
||||
return $this->from(Config::get("mail.from"))
|
||||
->to($this->email)
|
||||
->subject($subject)
|
||||
->view('emails.oauth2_passwordless_otp');
|
||||
}
|
||||
}
|
@ -71,13 +71,13 @@ final class WelcomeNewUserEmail extends Mailable
|
||||
public function build()
|
||||
{
|
||||
|
||||
$subject = sprintf("%s email verification needed", Config::get('app.app_name'));
|
||||
$subject = sprintf("Thank you for registering for an %s account", Config::get('app.app_name'));
|
||||
$view = 'emails.welcome_new_user_email';
|
||||
|
||||
if(Config::get("app.tenant_name") == 'FNTECH') {
|
||||
$view = 'emails.welcome_new_user_email_fn';
|
||||
$subject = sprintf("Thank you for registering for an %s account", Config::get('app.app_name'));
|
||||
}
|
||||
|
||||
Log::debug(sprintf("WelcomeNewUserEmail::build to %s", $this->user_email));
|
||||
return $this->from(Config::get("mail.from"))
|
||||
->to($this->user_email)
|
||||
|
@ -15,6 +15,7 @@
|
||||
use App\libs\Utils\URLUtils;
|
||||
use Auth\User;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use jwa\cryptographic_algorithms\ContentEncryptionAlgorithms_Registry;
|
||||
use jwa\cryptographic_algorithms\DigitalSignatures_MACs_Registry;
|
||||
@ -90,6 +91,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 +362,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 +383,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 +399,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 +425,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 = [
|
||||
@ -443,7 +470,9 @@ class Client extends BaseEntity implements IClient
|
||||
$this->getApplicationType() == IClient::ApplicationType_Native ||
|
||||
$this->getApplicationType() == IClient::ApplicationType_Web_App ||
|
||||
// PCKE
|
||||
$this->pkce_enabled;
|
||||
$this->pkce_enabled ||
|
||||
// Passwordless
|
||||
$this->otp_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1603,4 +1632,107 @@ 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->otp_length = intval(Config::get("otp.length"));
|
||||
$this->otp_lifetime = intval(Config::get("otp.lifetime"));
|
||||
}
|
||||
|
||||
public function disablePasswordless(): void
|
||||
{
|
||||
$this->otp_enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOtpLength(): int
|
||||
{
|
||||
$res = $this->otp_length;
|
||||
if(is_null($res)){
|
||||
$res = intval(Config::get("otp.length"));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $otp_length
|
||||
*/
|
||||
public function setOtpLength(int $otp_length): void
|
||||
{
|
||||
$this->otp_length = $otp_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOtpLifetime(): int
|
||||
{
|
||||
$res = $this->otp_lifetime;
|
||||
|
||||
if(is_null($res)){
|
||||
$res = intval(Config::get("otp.lifetime"));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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));
|
||||
}
|
||||
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
|
||||
use App\libs\Utils\URLUtils;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\ResourceServer;
|
||||
use OAuth2\Models\IClient;
|
||||
@ -199,14 +200,32 @@ final class ClientFactory
|
||||
$client->disablePCKE();
|
||||
}
|
||||
|
||||
if(isset($payload['otp_enabled'])) {
|
||||
$otp_enabled = boolval($payload['otp_enabled']);
|
||||
if($otp_enabled)
|
||||
$client->enablePasswordless();
|
||||
else
|
||||
$client->disablePasswordless();
|
||||
}
|
||||
|
||||
if(isset($payload['otp_length'])){
|
||||
$client->setOtpLength(intval($payload['otp_length']));
|
||||
}
|
||||
|
||||
if(isset($payload['otp_lifetime'])){
|
||||
$client->setOtpLifetime(intval($payload['otp_lifetime']));
|
||||
}
|
||||
|
||||
$scope_repository = App::make(IApiScopeRepository::class);
|
||||
//add default scopes
|
||||
foreach ($scope_repository->getDefaults() as $default_scope) {
|
||||
Log::debug(sprintf("ClientFactory::populate processing scope %s", $default_scope->getName()));
|
||||
if
|
||||
(
|
||||
$default_scope->getName() === OAuth2Protocol::OfflineAccess_Scope
|
||||
&& !$client->canRequestRefreshTokens()
|
||||
) {
|
||||
Log::debug(sprintf("ClientFactory::populate skipping scope %s", $default_scope->getName()));
|
||||
continue;
|
||||
}
|
||||
$client->addScope($default_scope);
|
||||
|
117
app/Models/OAuth2/Factories/OTPFactory.php
Normal file
117
app/Models/OAuth2/Factories/OTPFactory.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?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->setScope($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
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
*/
|
||||
static public function buildFromPayload
|
||||
(
|
||||
array $payload,
|
||||
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($payload[OAuth2Protocol::OAuth2PasswordlessConnection]);
|
||||
$otp->setSend($payload[OAuth2Protocol::OAuth2PasswordlessSend]);
|
||||
$otp->setScope($payload[OAuth2Protocol::OAuth2Protocol_Scope] ?? null);
|
||||
$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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
467
app/Models/OAuth2/OAuth2OTP.php
Normal file
467
app/Models/OAuth2/OAuth2OTP.php
Normal file
@ -0,0 +1,467 @@
|
||||
<?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="scope", type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $scope;
|
||||
|
||||
/**
|
||||
* @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 getScope(): ?string
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scope
|
||||
*/
|
||||
public function setScope(?string $scope): void
|
||||
{
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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->scope);
|
||||
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->scope = $request->getScopes();
|
||||
$instance->value = $request->getOTP();
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user_name
|
||||
* @param string $connection
|
||||
* @param string $value
|
||||
* @return OAuth2OTP|null
|
||||
*/
|
||||
public static function fromParams(string $user_name, string $connection, string $value):?OAuth2OTP{
|
||||
$instance = new self(strlen($value));
|
||||
$instance->connection = $connection;
|
||||
if($connection == OAuth2Protocol::OAuth2PasswordlessConnectionEmail)
|
||||
$instance->email = $user_name;
|
||||
if($connection == OAuth2Protocol::OAuth2PasswordlessConnectionEmail)
|
||||
$instance->phone_number = $user_name;
|
||||
$instance->value = $value;
|
||||
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")
|
||||
|
@ -13,7 +13,12 @@
|
||||
**/
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
/**
|
||||
* Class RouteServiceProvider
|
||||
* @package App\Providers
|
||||
@ -38,6 +43,28 @@ final class RouteServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
parent::boot();
|
||||
$this->configureRateLimiting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('account', function (Request $request) {
|
||||
return Limit::perMinute(5)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
|
||||
RateLimiter::for('otp', function (Request $request) {
|
||||
return Limit::perMinute(10)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
|
||||
RateLimiter::for('oauth2', function (Request $request) {
|
||||
$maxAttempts = App::environment() == "testing" ? PHP_INT_MAX : 50;
|
||||
return Limit::perMinute($maxAttempts)->by(optional($request->user())->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
93
app/Repositories/DoctrineOAuth2OTPRepository.php
Normal file
93
app/Repositories/DoctrineOAuth2OTPRepository.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user_name
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP[]
|
||||
*/
|
||||
public function getByUserNameNotRedeemed
|
||||
(
|
||||
string $user_name,
|
||||
?Client $client = null
|
||||
)
|
||||
{
|
||||
$query = $this->getEntityManager()
|
||||
->createQueryBuilder()
|
||||
->select("e")
|
||||
->from($this->getBaseEntity(), "e")
|
||||
->andWhere("(e.email = (:user_name) or e.phone_number = (:user_name))")
|
||||
->andWhere("e.redeemed_at is null")
|
||||
->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()->getResult();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -273,7 +273,7 @@ final class ClientService extends AbstractService implements IClientService
|
||||
|
||||
return $this->tx_service->transaction(function () use ($id, $payload) {
|
||||
|
||||
$editing_user = $this->auth_service->getCurrentUser();
|
||||
$editing_user = $this->auth_service->getCurrentUser();
|
||||
|
||||
$client = $this->client_repository->getById($id);
|
||||
|
||||
|
@ -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,13 @@
|
||||
|
||||
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\Exceptions\AuthenticationException;
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@ -23,6 +29,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 +57,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 +76,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 +110,10 @@ final class TokenService extends AbstractService implements ITokenService
|
||||
* @var IClientService
|
||||
*/
|
||||
private $client_service;
|
||||
/**
|
||||
* @var IUserService
|
||||
*/
|
||||
private $user_service;
|
||||
/**
|
||||
* @var ILockManagerService
|
||||
*/
|
||||
@ -123,15 +137,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 +193,34 @@ 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 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,146 @@ final class TokenService extends AbstractService implements ITokenService
|
||||
return $this->getAccessToken($db_access_token->getValue(), true);
|
||||
}
|
||||
|
||||
private function postCreateOTP(OAuth2OTP $otp,?Client $client):OAuth2OTP{
|
||||
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 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){
|
||||
|
||||
return $this->postCreateOTP
|
||||
(
|
||||
OTPFactory::buildFromRequest($request, $this->identifier_generator, $client),
|
||||
$client
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $payload
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createOTPFromPayload(array $payload, ?Client $client):OAuth2OTP{
|
||||
return $this->tx_service->transaction(function() use($payload, $client){
|
||||
|
||||
$otp = $this->postCreateOTP
|
||||
(
|
||||
OTPFactory::buildFromPayload($payload, $this->identifier_generator, $client),
|
||||
$client
|
||||
);
|
||||
if(is_null($client)){
|
||||
foreach($this->otp_repository->getByUserNameNotRedeemed($otp->getUserName()) as $formerOTP){
|
||||
$this->otp_repository->delete($formerOTP);
|
||||
}
|
||||
$this->otp_repository->add($otp);
|
||||
}
|
||||
return $otp;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otp
|
||||
* @param Client|null $client
|
||||
* @return AccessToken
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createAccessTokenFromOTP(OAuth2OTP $otp, ?Client $client): AccessToken
|
||||
{
|
||||
|
||||
try {
|
||||
$otp = $this->auth_service->loginWithOTP($otp, $client);
|
||||
// build current audience ...
|
||||
$audience = $this->scope_service->getStrAudienceByScopeNames
|
||||
(
|
||||
explode
|
||||
(
|
||||
OAuth2Protocol::OAuth2Protocol_Scope_Delimiter,
|
||||
$otp->getScope()
|
||||
)
|
||||
);
|
||||
|
||||
$access_token = $this->identifier_generator->generate
|
||||
(
|
||||
AccessToken::createFromOTP
|
||||
(
|
||||
$otp,
|
||||
! is_null($client) ? $client->getClientId() : null,
|
||||
$audience,
|
||||
$this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime')
|
||||
)
|
||||
);
|
||||
|
||||
return $this->tx_service->transaction(function() use($access_token, $client){
|
||||
// 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($this->auth_service->getCurrentUser());
|
||||
|
||||
$this->access_token_repository->add($access_token_db);
|
||||
|
||||
//check if use refresh tokens...
|
||||
|
||||
if
|
||||
(
|
||||
$client->useRefreshToken() &&
|
||||
$client->isPasswordlessEnabled() &&
|
||||
str_contains($access_token->getScope(), 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;
|
||||
});
|
||||
}
|
||||
catch (AuthenticationException $ex){
|
||||
throw new InvalidOTPException($ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
27
app/Strategies/OTP/IOTPChannelStrategy.php
Normal file
27
app/Strategies/OTP/IOTPChannelStrategy.php
Normal file
@ -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;
|
||||
}
|
22
app/Strategies/OTP/IOTPTypeBuilderStrategy.php
Normal file
22
app/Strategies/OTP/IOTPTypeBuilderStrategy.php
Normal file
@ -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;
|
||||
}
|
53
app/Strategies/OTP/OTPChannelEmailStrategy.php
Normal file
53
app/Strategies/OTP/OTPChannelEmailStrategy.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
31
app/Strategies/OTP/OTPChannelStrategyFactory.php
Normal file
31
app/Strategies/OTP/OTPChannelStrategyFactory.php
Normal file
@ -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));
|
||||
}
|
||||
}
|
34
app/Strategies/OTP/OTPTypeBuilderStrategyFactory.php
Normal file
34
app/Strategies/OTP/OTPTypeBuilderStrategyFactory.php
Normal file
@ -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));
|
||||
}
|
||||
}
|
31
app/Strategies/OTP/OTPTypeCodeBuilderStrategy.php
Normal file
31
app/Strategies/OTP/OTPTypeCodeBuilderStrategy.php
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -11,29 +11,38 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use App\libs\OAuth2\Exceptions\ReloadSessionException;
|
||||
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||
use App\Services\AbstractService;
|
||||
use Auth\Exceptions\AuthenticationException;
|
||||
use Auth\Repositories\IUserRepository;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Exceptions\InvalidOTPException;
|
||||
use OAuth2\Models\IClient;
|
||||
use OAuth2\Services\IPrincipalService;
|
||||
use OpenId\Models\IOpenIdUser;
|
||||
use OpenId\Services\IUserService;
|
||||
use App\Services\Auth\IUserService as IAuthUserService;
|
||||
use utils\Base64UrlRepresentation;
|
||||
use Utils\Db\ITransactionService;
|
||||
use Utils\Services\IAuthService;
|
||||
use Utils\Services\ICacheService;
|
||||
use jwe\compression_algorithms\CompressionAlgorithms_Registry;
|
||||
use jwe\compression_algorithms\CompressionAlgorithmsNames;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AuthService
|
||||
* @package Auth
|
||||
*/
|
||||
final class AuthService implements IAuthService
|
||||
final class AuthService extends AbstractService implements IAuthService
|
||||
{
|
||||
/**
|
||||
* @var IPrincipalService
|
||||
@ -53,25 +62,44 @@ final class AuthService implements IAuthService
|
||||
*/
|
||||
private $user_repository;
|
||||
|
||||
/**
|
||||
* @var IAuthUserService
|
||||
*/
|
||||
private $auth_user_service;
|
||||
|
||||
/**
|
||||
* @var IOAuth2OTPRepository
|
||||
*/
|
||||
private $otp_repository;
|
||||
|
||||
/**
|
||||
* AuthService constructor.
|
||||
* @param IUserRepository $user_repository
|
||||
* @param IOAuth2OTPRepository $otp_repository
|
||||
* @param IPrincipalService $principal_service
|
||||
* @param IUserService $user_service
|
||||
* @param ICacheService $cache_service
|
||||
* @param IAuthUserService $auth_user_service
|
||||
* @param ITransactionService $tx_service
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
IUserRepository $user_repository,
|
||||
IOAuth2OTPRepository $otp_repository,
|
||||
IPrincipalService $principal_service,
|
||||
IUserService $user_service,
|
||||
ICacheService $cache_service
|
||||
ICacheService $cache_service,
|
||||
IAuthUserService $auth_user_service,
|
||||
ITransactionService $tx_service
|
||||
)
|
||||
{
|
||||
$this->user_repository = $user_repository;
|
||||
parent::__construct($tx_service);
|
||||
$this->user_repository = $user_repository;
|
||||
$this->principal_service = $principal_service;
|
||||
$this->user_service = $user_service;
|
||||
$this->cache_service = $cache_service;
|
||||
$this->user_service = $user_service;
|
||||
$this->cache_service = $cache_service;
|
||||
$this->auth_user_service = $auth_user_service;
|
||||
$this->otp_repository = $otp_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,7 +113,7 @@ final class AuthService implements IAuthService
|
||||
/**
|
||||
* @return User|null
|
||||
*/
|
||||
public function getCurrentUser():?User
|
||||
public function getCurrentUser(): ?User
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
@ -94,25 +122,96 @@ final class AuthService implements IAuthService
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param bool $remember_me
|
||||
* @return mixed
|
||||
* @return bool
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function login($username, $password, $remember_me)
|
||||
public function login(string $username, string $password, bool $remember_me): bool
|
||||
{
|
||||
Log::debug("AuthService::login");
|
||||
$res = Auth::attempt(['username' => $username, 'password' => $password], $remember_me);
|
||||
|
||||
if ($res)
|
||||
{
|
||||
Log::debug("AuthService::login: clearing principal");
|
||||
$this->principal_service->clear();
|
||||
$this->principal_service->register
|
||||
(
|
||||
$this->getCurrentUser()->getId(),
|
||||
time()
|
||||
);
|
||||
$this->last_login_error = "";
|
||||
if (!Auth::attempt(['username' => $username, 'password' => $password], $remember_me)) {
|
||||
throw new AuthenticationException("We are sorry, your username or password does not match an existing record.");
|
||||
}
|
||||
|
||||
return $res;
|
||||
Log::debug("AuthService::login: clearing principal");
|
||||
$this->principal_service->clear();
|
||||
$this->principal_service->register
|
||||
(
|
||||
$this->getCurrentUser()->getId(),
|
||||
time()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otpClaim
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP|null
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null): ?OAuth2OTP{
|
||||
|
||||
$otp = $this->tx_service->transaction(function() use($otpClaim, $client){
|
||||
// find first db OTP by connection , by username (email/phone) number and client not redeemed
|
||||
$otp = $this->otp_repository->getByConnectionAndUserNameNotRedeemed
|
||||
(
|
||||
$otpClaim->getConnection(),
|
||||
$otpClaim->getUserName(),
|
||||
$client
|
||||
);
|
||||
if(is_null($otp)){
|
||||
// otp no emitted
|
||||
throw new AuthenticationException("Non existent OTP.");
|
||||
}
|
||||
$otp->logRedeemAttempt();
|
||||
return $otp;
|
||||
});
|
||||
|
||||
return $this->tx_service->transaction(function() use($otp, $otpClaim, $client){
|
||||
|
||||
if (!$otp->isAlive()) {
|
||||
throw new AuthenticationException("OTP is expired.");
|
||||
}
|
||||
|
||||
if (!$otp->isValid()) {
|
||||
throw new AuthenticationException( "OTP is not valid.");
|
||||
}
|
||||
|
||||
if ($otp->getValue() != $otpClaim->getValue()) {
|
||||
throw new AuthenticationException("OTP mismatch.");
|
||||
}
|
||||
|
||||
if(!empty($otpClaim->getScope()) && !$otp->allowScope($otpClaim->getScope()))
|
||||
throw new InvalidOTPException("OTP Requested scopes escalates former scopes.");
|
||||
|
||||
if (($otp->hasClient() && is_null($client)) ||
|
||||
($otp->hasClient() && !is_null($client) && $client->getClientId() != $otp->getClient()->getClientId())) {
|
||||
throw new AuthenticationException("OTP audience mismatch.");
|
||||
}
|
||||
|
||||
$user = $this->getUserByUsername($otp->getUserName());
|
||||
|
||||
if (is_null($user)) {
|
||||
// we need to create a new one ( auto register)
|
||||
|
||||
Log::debug(sprintf("AuthService::loginWithOTP user %s does not exists ...", $otp->getUserName()));
|
||||
|
||||
$user = $this->auth_user_service->registerUser([
|
||||
'email' => $otp->getUserName(),
|
||||
'email_verified' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$otp->setAuthTime(time());
|
||||
$otp->setUserId($user->getId());
|
||||
$otp->redeem();
|
||||
|
||||
Auth::login($user, false);
|
||||
|
||||
return $otp;
|
||||
});
|
||||
}
|
||||
|
||||
public function logout()
|
||||
@ -140,8 +239,7 @@ final class AuthService implements IAuthService
|
||||
*/
|
||||
public function getUserAuthorizationResponse()
|
||||
{
|
||||
if (Session::has("openid.authorization.response"))
|
||||
{
|
||||
if (Session::has("openid.authorization.response")) {
|
||||
$value = Session::get("openid.authorization.response");
|
||||
|
||||
return $value;
|
||||
@ -153,8 +251,7 @@ final class AuthService implements IAuthService
|
||||
|
||||
public function clearUserAuthorizationResponse()
|
||||
{
|
||||
if (Session::has("openid.authorization.response"))
|
||||
{
|
||||
if (Session::has("openid.authorization.response")) {
|
||||
Session::remove("openid.authorization.response");
|
||||
Session::save();
|
||||
}
|
||||
@ -170,7 +267,7 @@ final class AuthService implements IAuthService
|
||||
* @param string $openid
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUserByOpenId(string $openid):?User
|
||||
public function getUserByOpenId(string $openid): ?User
|
||||
{
|
||||
return $this->user_repository->getByIdentifier($openid);
|
||||
}
|
||||
@ -179,7 +276,7 @@ final class AuthService implements IAuthService
|
||||
* @param string $username
|
||||
* @return null|User
|
||||
*/
|
||||
public function getUserByUsername(string $username):?User
|
||||
public function getUserByUsername(string $username): ?User
|
||||
{
|
||||
return $this->user_repository->getByEmailOrName($username);
|
||||
}
|
||||
@ -188,7 +285,7 @@ final class AuthService implements IAuthService
|
||||
* @param int $id
|
||||
* @return null|User
|
||||
*/
|
||||
public function getUserById(int $id):?User
|
||||
public function getUserById(int $id): ?User
|
||||
{
|
||||
return $this->user_repository->getById($id);
|
||||
}
|
||||
@ -212,26 +309,25 @@ final class AuthService implements IAuthService
|
||||
|
||||
public function clearUserAuthenticationResponse()
|
||||
{
|
||||
if (Session::has("openstackid.authentication.response"))
|
||||
{
|
||||
if (Session::has("openstackid.authentication.response")) {
|
||||
Session::remove("openstackid.authentication.response");
|
||||
Session::save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user_id
|
||||
* @param string $user_id
|
||||
* @return string
|
||||
*/
|
||||
public function unwrapUserId(string $user_id):string
|
||||
public function unwrapUserId(string $user_id): string
|
||||
{
|
||||
$user = $this->getUserById(intval($user_id));
|
||||
|
||||
if(!is_null($user))
|
||||
if (!is_null($user))
|
||||
return $user_id;
|
||||
|
||||
$unwrapped_name = $this->decrypt($user_id);
|
||||
$parts = explode(':', $unwrapped_name);
|
||||
$parts = explode(':', $unwrapped_name);
|
||||
return intval($parts[1]);
|
||||
}
|
||||
|
||||
@ -240,10 +336,10 @@ final class AuthService implements IAuthService
|
||||
* @param IClient $client
|
||||
* @return string
|
||||
*/
|
||||
public function wrapUserId(int $user_id, IClient $client):string
|
||||
public function wrapUserId(int $user_id, IClient $client): string
|
||||
{
|
||||
if($client->getSubjectType() === IClient::SubjectType_Public)
|
||||
return $user_id;
|
||||
if ($client->getSubjectType() === IClient::SubjectType_Public)
|
||||
return $user_id;
|
||||
|
||||
$wrapped_name = sprintf('%s:%s', $client->getClientId(), $user_id);
|
||||
return $this->encrypt($wrapped_name);
|
||||
@ -253,7 +349,7 @@ final class AuthService implements IAuthService
|
||||
* @param string $value
|
||||
* @return String
|
||||
*/
|
||||
private function encrypt(string $value):string
|
||||
private function encrypt(string $value): string
|
||||
{
|
||||
return base64_encode(Crypt::encrypt($value));
|
||||
}
|
||||
@ -262,7 +358,7 @@ final class AuthService implements IAuthService
|
||||
* @param string $value
|
||||
* @return String
|
||||
*/
|
||||
private function decrypt(string $value):string
|
||||
private function decrypt(string $value): string
|
||||
{
|
||||
$value = base64_decode($value);
|
||||
return Crypt::decrypt($value);
|
||||
@ -271,16 +367,16 @@ final class AuthService implements IAuthService
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionId():string
|
||||
public function getSessionId(): string
|
||||
{
|
||||
return Session::getId();
|
||||
return Session::getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $client_id
|
||||
* @return void
|
||||
*/
|
||||
public function registerRPLogin(string $client_id):void
|
||||
public function registerRPLogin(string $client_id): void
|
||||
{
|
||||
|
||||
try {
|
||||
@ -292,15 +388,14 @@ final class AuthService implements IAuthService
|
||||
$rps = $zlib->uncompress($rps);
|
||||
$rps .= '|';
|
||||
}
|
||||
if(is_null($rps)) $rps = "";
|
||||
if (is_null($rps)) $rps = "";
|
||||
|
||||
if (!str_contains($rps, $client_id))
|
||||
$rps .= $client_id;
|
||||
|
||||
$rps = $zlib->compress($rps);
|
||||
$rps = $this->encrypt($rps);
|
||||
}
|
||||
catch(Exception $ex){
|
||||
} catch (Exception $ex) {
|
||||
Log::warning($ex);
|
||||
$rps = "";
|
||||
}
|
||||
@ -322,13 +417,12 @@ final class AuthService implements IAuthService
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLoggedRPs():array
|
||||
public function getLoggedRPs(): array
|
||||
{
|
||||
$rps = Cookie::get(IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME);
|
||||
$rps = Cookie::get(IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME);
|
||||
$zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib);
|
||||
|
||||
if(!empty($rps))
|
||||
{
|
||||
if (!empty($rps)) {
|
||||
$rps = $this->decrypt($rps);
|
||||
$rps = $zlib->uncompress($rps);
|
||||
return explode('|', $rps);
|
||||
@ -340,29 +434,28 @@ final class AuthService implements IAuthService
|
||||
* @param string $jti
|
||||
* @throws Exception
|
||||
*/
|
||||
public function reloadSession(string $jti):void
|
||||
public function reloadSession(string $jti): void
|
||||
{
|
||||
Log::debug(sprintf("AuthService::reloadSession jti %s", $jti ));
|
||||
Log::debug(sprintf("AuthService::reloadSession jti %s", $jti));
|
||||
$session_id = $this->cache_service->getSingleValue($jti);
|
||||
|
||||
Log::debug(sprintf("AuthService::reloadSession session_id %s", $session_id ));
|
||||
if(empty($session_id))
|
||||
Log::debug(sprintf("AuthService::reloadSession session_id %s", $session_id));
|
||||
if (empty($session_id))
|
||||
throw new ReloadSessionException('session not found!');
|
||||
|
||||
if($this->cache_service->exists($session_id."invalid")){
|
||||
if ($this->cache_service->exists($session_id . "invalid")) {
|
||||
// session was marked as void, check if we are authenticated
|
||||
if(!Auth::check())
|
||||
if (!Auth::check())
|
||||
throw new ReloadSessionException('user not found!');
|
||||
}
|
||||
|
||||
Session::setId(Crypt::decrypt($session_id));
|
||||
Session::start();
|
||||
if(!Auth::check())
|
||||
{
|
||||
$user_id = $this->principal_service->get()->getUserId();
|
||||
Log::debug(sprintf("AuthService::reloadSession user_id %s", $user_id ));
|
||||
$user = $this->getUserById($user_id);
|
||||
if(is_null($user))
|
||||
if (!Auth::check()) {
|
||||
$user_id = $this->principal_service->get()->getUserId();
|
||||
Log::debug(sprintf("AuthService::reloadSession user_id %s", $user_id));
|
||||
$user = $this->getUserById($user_id);
|
||||
if (is_null($user))
|
||||
throw new ReloadSessionException('user not found!');
|
||||
Auth::login($user);
|
||||
}
|
||||
@ -373,19 +466,21 @@ final class AuthService implements IAuthService
|
||||
* @param int $id_token_lifetime
|
||||
* @return string
|
||||
*/
|
||||
public function generateJTI(string $client_id, int $id_token_lifetime):string {
|
||||
$session_id = Crypt::encrypt(Session::getId());
|
||||
$encoder = new Base64UrlRepresentation();
|
||||
$jti = $encoder->encode(hash('sha512', $session_id.$client_id, true));
|
||||
public function generateJTI(string $client_id, int $id_token_lifetime): string
|
||||
{
|
||||
$session_id = Crypt::encrypt(Session::getId());
|
||||
$encoder = new Base64UrlRepresentation();
|
||||
$jti = $encoder->encode(hash('sha512', $session_id . $client_id, true));
|
||||
|
||||
$this->cache_service->addSingleValue($jti, $session_id, $id_token_lifetime);
|
||||
|
||||
return $jti;
|
||||
}
|
||||
|
||||
|
||||
public function invalidateSession():void {
|
||||
$session_id = Crypt::encrypt(Session::getId());
|
||||
$this->cache_service->addSingleValue($session_id."invalid", $session_id);
|
||||
public function invalidateSession(): void
|
||||
{
|
||||
$session_id = Crypt::encrypt(Session::getId());
|
||||
$this->cache_service->addSingleValue($session_id . "invalid", $session_id);
|
||||
}
|
||||
|
||||
}
|
@ -21,7 +21,6 @@ class AuthenticationException extends Exception
|
||||
|
||||
public function __construct($message = "")
|
||||
{
|
||||
$message = "Authentication Exception : " . $message;
|
||||
parent::__construct($message, 0, null);
|
||||
}
|
||||
}
|
@ -474,14 +474,18 @@ class User extends BaseEntity
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function getEmail()
|
||||
public function getEmail():string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFullName(): ?string
|
||||
{
|
||||
return $this->getFirstName() . " " . $this->getLastName();
|
||||
$full_name = $this->getFirstName() . " " . $this->getLastName();
|
||||
return !empty(trim($full_name)) ? $full_name : $this->email;
|
||||
}
|
||||
|
||||
public function getFirstName()
|
||||
|
28
app/libs/OAuth2/Exceptions/InvalidOTPException.php
Normal file
28
app/libs/OAuth2/Exceptions/InvalidOTPException.php
Normal file
@ -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;
|
||||
|
@ -90,10 +90,10 @@ abstract class AbstractGrantType implements IGrantType
|
||||
*/
|
||||
public function completeFlow(OAuth2Request $request)
|
||||
{
|
||||
//get client credentials from request..
|
||||
// get client credentials from request..
|
||||
$this->client_auth_context = $this->client_service->getCurrentClientAuthInfo();
|
||||
|
||||
//retrieve client from storage..
|
||||
// retrieve client from storage..
|
||||
$this->current_client = $this->client_repository->getClientById($this->client_auth_context->getId());
|
||||
|
||||
if (is_null($this->current_client))
|
||||
|
379
app/libs/OAuth2/GrantTypes/PasswordlessGrantType.php
Normal file
379
app/libs/OAuth2/GrantTypes/PasswordlessGrantType.php
Normal file
@ -0,0 +1,379 @@
|
||||
<?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 &&
|
||||
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->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->getScope()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth2Request $request
|
||||
* @return OAuth2Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(OAuth2Request $request)
|
||||
{
|
||||
try {
|
||||
|
||||
if (!($request instanceof OAuth2PasswordlessAuthenticationRequest)) {
|
||||
throw new InvalidOAuth2Request;
|
||||
}
|
||||
|
||||
if(!$request->isValid()){
|
||||
throw new InvalidOAuth2Request($request->getLastValidationError());
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
if(!$request->isValid()){
|
||||
throw new InvalidOAuth2Request($request->getLastValidationError());
|
||||
}
|
||||
|
||||
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->getScope();
|
||||
// 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);
|
||||
|
||||
|
53
app/libs/OAuth2/Repositories/IOAuth2OTPRepository.php
Normal file
53
app/libs/OAuth2/Repositories/IOAuth2OTPRepository.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @param string $user_name
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP[]
|
||||
*/
|
||||
public function getByUserNameNotRedeemed
|
||||
(
|
||||
string $user_name,
|
||||
?Client $client = null
|
||||
);
|
||||
}
|
@ -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,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $optional_params = [
|
||||
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,31 @@ 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 array $payload
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createOTPFromPayload(array $payload, ?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()));
|
||||
@ -51,6 +51,6 @@ final class ClientPKCEAuthContextValidator implements IClientAuthContextValidato
|
||||
|
||||
Log::debug(sprintf("ClientPKCEAuthContextValidator::validate client id %s - provide client id %s", $client->getClientId(), $providedClientId));
|
||||
|
||||
return $client->getClientId() === $providedClientId && $client->isPKCEEnabled();
|
||||
return $client->getClientId() === $providedClientId && ( $client->isPKCEEnabled() || $client->isPasswordlessEnabled());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
84
app/libs/Utils/Model/AbstractIdentifier.php
Normal file
84
app/libs/Utils/Model/AbstractIdentifier.php
Normal file
@ -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;
|
||||
}
|
@ -12,7 +12,10 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Auth\Exceptions\AuthenticationException;
|
||||
use Auth\User;
|
||||
use Models\OAuth2\Client;
|
||||
use Models\OAuth2\OAuth2OTP;
|
||||
use OAuth2\Models\IClient;
|
||||
use OpenId\Models\IOpenIdUser;
|
||||
/**
|
||||
@ -33,6 +36,8 @@ interface IAuthService
|
||||
const AuthenticationResponse_None = "None";
|
||||
const AuthenticationResponse_Cancel = "Cancel";
|
||||
|
||||
const AuthenticationFlowPassword = "password";
|
||||
const AuthenticationFlowPasswordless = "otp";
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@ -47,9 +52,19 @@ interface IAuthService
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param bool $remember_me
|
||||
* @return mixed
|
||||
* @return bool
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function login($username, $password, $remember_me);
|
||||
public function login(string $username, string $password, bool $remember_me): bool;
|
||||
|
||||
/**
|
||||
* @param OAuth2OTP $otpClaim
|
||||
* @param Client|null $client
|
||||
* @return OAuth2OTP|null
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null): ?OAuth2OTP;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
|
@ -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);
|
||||
|
||||
}
|
7
config/otp.php
Normal file
7
config/otp.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
|
||||
return [
|
||||
"lifetime" => env("OTP_DEFAULT_LIFETIME", 120),
|
||||
"length" => env("OTP_DEFAULT_LENGTH", 6)
|
||||
];
|
93
database/migrations/Version20210616123839.php
Normal file
93
database/migrations/Version20210616123839.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?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->text("scope")->setNotnull(false);
|
||||
$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(true)->setDefault(6);
|
||||
// seconds
|
||||
$table->integer('lifetime')->setNotnull(true)->setDefault(60);
|
||||
$table->dateTime('redeemed_at')->setNotnull(false);
|
||||
// FK Optional
|
||||
$table->unsignedBigInteger("oauth2_client_id", 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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
53
database/migrations/Version20210616123841.php
Normal file
53
database/migrations/Version20210616123841.php
Normal file
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -24,6 +24,31 @@ jQuery(document).ready(function($){
|
||||
}
|
||||
});
|
||||
|
||||
if($("#otp_enabled") .is(":checked")){
|
||||
$(".otp_controls").removeClass("hidden");
|
||||
$("#otp_length").rules("add", {required:true, min:4, max:8});
|
||||
$("#otp_lifetime").rules("add", {required:true, min:60, max:600});
|
||||
}
|
||||
else {
|
||||
$(".otp_controls").addClass("hidden");
|
||||
$("#otp_length").rules("remove");
|
||||
$("#otp_lifetime").rules("remove");
|
||||
}
|
||||
|
||||
|
||||
$("#otp_enabled").change(function() {
|
||||
if(this.checked) {
|
||||
$(".otp_controls").removeClass("hidden");
|
||||
$("#otp_length").rules("add", {required:true, min:4, max:8});
|
||||
$("#otp_lifetime").rules("add", {required:true, min:60, max:600});
|
||||
return true;
|
||||
}
|
||||
$(".otp_controls").addClass("hidden");
|
||||
$("#otp_length").rules("remove");
|
||||
$("#otp_lifetime").rules("remove");
|
||||
return true;
|
||||
});
|
||||
|
||||
$('#token_endpoint_auth_method').change(function() {
|
||||
var auth_method = $(this).val();
|
||||
|
||||
|
@ -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
|
||||
|
@ -20,7 +20,7 @@ const xhrs = {};
|
||||
|
||||
const cancel = (key) => {
|
||||
if(xhrs[key]) {
|
||||
xhrs[key].abort();
|
||||
xhrs[key].xhr.abort();
|
||||
console.log(`aborted request ${key}`);
|
||||
delete xhrs[key];
|
||||
}
|
||||
@ -31,11 +31,15 @@ const schedule = (key, req) => {
|
||||
xhrs[key] = req;
|
||||
};
|
||||
|
||||
const end = (key) => {
|
||||
delete xhrs[key];
|
||||
}
|
||||
|
||||
const isObjectEmpty = (obj) => {
|
||||
return Object.keys(obj).length === 0 && obj.constructor === Object ;
|
||||
}
|
||||
|
||||
export const getRawRequest = (endpoint, errorHandler = null) => (params) => {
|
||||
export const getRawRequest = (endpoint) => (params) => {
|
||||
let url = URI(endpoint);
|
||||
|
||||
if(!isObjectEmpty(params))
|
||||
@ -45,26 +49,47 @@ export const getRawRequest = (endpoint, errorHandler = null) => (params) => {
|
||||
|
||||
cancel(key);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = http.get(url.toString())
|
||||
.timeout({
|
||||
response: 60000,
|
||||
deadline: 60000,
|
||||
})
|
||||
.end(
|
||||
(err, res) => {
|
||||
if (err || !res.ok) {
|
||||
if(errorHandler) {
|
||||
errorHandler(err, res);
|
||||
}
|
||||
return reject({ err, res })
|
||||
}
|
||||
let json = res.body;
|
||||
return resolve({response: json});
|
||||
}
|
||||
)
|
||||
let req = http.get(url.toString());
|
||||
schedule(key, req);
|
||||
|
||||
return req.timeout({
|
||||
response: 60000,
|
||||
deadline: 60000,
|
||||
}).then((res) => {
|
||||
let json = res.body;
|
||||
end(key);
|
||||
return Promise.resolve({response: json});
|
||||
}).catch((error) => {
|
||||
end(key);
|
||||
return Promise.reject(error);
|
||||
})
|
||||
}
|
||||
|
||||
export const postRawRequest = (endpoint) => (params, headers = {}) => {
|
||||
let url = URI(endpoint);
|
||||
|
||||
if(!isObjectEmpty(params))
|
||||
url = url.query(params);
|
||||
|
||||
let key = url.toString();
|
||||
|
||||
cancel(key);
|
||||
|
||||
let req = http.post(url.toString());
|
||||
|
||||
schedule(key, req);
|
||||
|
||||
return req.set(headers).send(params).timeout({
|
||||
response: 60000,
|
||||
deadline: 60000,
|
||||
}).then((res) => {
|
||||
let json = res.body;
|
||||
end(key);
|
||||
return Promise.resolve({response: json});
|
||||
}).catch((error) => {
|
||||
end(key);
|
||||
return Promise.reject(error);
|
||||
})
|
||||
|
||||
schedule(key, req);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {getRawRequest} from '../base_actions'
|
||||
|
||||
import {getRawRequest, postRawRequest} from '../base_actions'
|
||||
|
||||
|
||||
export const verifyAccount = (email) => {
|
||||
|
||||
const params = {
|
||||
email: email
|
||||
};
|
||||
@ -10,3 +10,13 @@ export const verifyAccount = (email) => {
|
||||
return getRawRequest(window.VERIFY_ACCOUNT_ENDPOINT)(params);
|
||||
|
||||
}
|
||||
|
||||
export const emitOTP = (email, token, connection = 'email', send='code') => {
|
||||
const params = {
|
||||
username: email,
|
||||
connection:connection,
|
||||
send:send
|
||||
}
|
||||
|
||||
return postRawRequest(window.EMIT_OTP_ENDPOINT)(params, {'X-CSRF-TOKEN': token});
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import Container from '@material-ui/core/Container';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import {verifyAccount} from './actions';
|
||||
import {verifyAccount, emitOTP} from './actions';
|
||||
import {MuiThemeProvider, createMuiTheme} from '@material-ui/core/styles';
|
||||
import DividerWithText from '../components/divider_with_text';
|
||||
import Visibility from '@material-ui/icons/Visibility';
|
||||
@ -23,7 +23,7 @@ import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import {emailValidator} from '../validator';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
|
||||
import Swal from 'sweetalert2'
|
||||
|
||||
const EmailInputForm = ({onValidateEmail, onHandleUserNameChange, disableInput, emailError}) => {
|
||||
|
||||
@ -117,6 +117,7 @@ const PasswordInputForm = ({
|
||||
/>
|
||||
<input type="hidden" value={userNameValue} id="username" name="username"/>
|
||||
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
|
||||
<input type="hidden" value="password" id="flow" name="flow"/>
|
||||
{shouldShowCaptcha() &&
|
||||
<ReCAPTCHA
|
||||
className={styles.recaptcha}
|
||||
@ -136,10 +137,91 @@ const PasswordInputForm = ({
|
||||
);
|
||||
}
|
||||
|
||||
const HelpLinks = ({forgotPasswordAction, verifyEmailAction, helpAction, appName}) => {
|
||||
const OTPInputForm = ({
|
||||
formAction,
|
||||
onAuthenticate,
|
||||
disableInput,
|
||||
showPassword,
|
||||
passwordValue,
|
||||
passwordError,
|
||||
onUserPasswordChange,
|
||||
handleClickShowPassword,
|
||||
handleMouseDownPassword,
|
||||
userNameValue,
|
||||
csrfToken,
|
||||
shouldShowCaptcha,
|
||||
captchaPublicKey,
|
||||
onChangeRecaptcha
|
||||
}) => {
|
||||
return(
|
||||
<form method="post" action={formAction} onSubmit={onAuthenticate}>
|
||||
<TextField
|
||||
id="password"
|
||||
name="password"
|
||||
disabled={disableInput}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={passwordValue}
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
label="Enter Your verification code"
|
||||
autoComplete="new-password"
|
||||
error={passwordError != ""}
|
||||
helperText={passwordError}
|
||||
onChange={onUserPasswordChange}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle code visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
>
|
||||
{showPassword ? <Visibility/> : <VisibilityOff/>}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
|
||||
/>
|
||||
<p>A Verification Code was just sent to your Email.</p>
|
||||
<FormControlLabel
|
||||
disabled={disableInput}
|
||||
control={<Checkbox value="remember" name="remember" id="remember" color="primary"/>}
|
||||
label="Remember me"
|
||||
/>
|
||||
<input type="hidden" value={userNameValue} id="username" name="username"/>
|
||||
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
|
||||
<input type="hidden" value="otp" id="flow" name="flow"/>
|
||||
<input type="hidden" value="email" id="connection" name="connection"/>
|
||||
{shouldShowCaptcha() &&
|
||||
<ReCAPTCHA
|
||||
className={styles.recaptcha}
|
||||
sitekey={captchaPublicKey}
|
||||
onChange={onChangeRecaptcha}
|
||||
/>
|
||||
}
|
||||
<Button variant="contained"
|
||||
disabled={disableInput}
|
||||
className={styles.continue_btn}
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={onAuthenticate}>
|
||||
Verify
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const HelpLinks = ({forgotPasswordAction, verifyEmailAction, helpAction, appName, emitOtpAction}) => {
|
||||
return (
|
||||
<>
|
||||
<hr className={styles.separator}/>
|
||||
<Link href="#" onClick={emitOtpAction} variant="body2" target="_self" >
|
||||
Get A Login Code emailed to you
|
||||
</Link>
|
||||
<Link href={forgotPasswordAction} target="_self" variant="body2">
|
||||
Forgot password?
|
||||
</Link>
|
||||
@ -153,6 +235,17 @@ const HelpLinks = ({forgotPasswordAction, verifyEmailAction, helpAction, appName
|
||||
);
|
||||
}
|
||||
|
||||
const OTPHelpLinks = ({emitOtpAction}) => {
|
||||
return (
|
||||
<>
|
||||
<hr className={styles.separator}/>
|
||||
<p className={styles.otp_p}>Didn't receive it ?</p>
|
||||
<p className={styles.otp_p}>Check your spam folder or <Link href="#" onClick={emitOtpAction} variant="body2" target="_self">resend email.</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const EmailErrorActions = ({createAccountAction, onValidateEmail, disableInput}) => {
|
||||
return(
|
||||
<Grid container style={{alignItems: 'center', marginTop: "20%"}}>
|
||||
@ -201,6 +294,7 @@ class LoginPage extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
user_name: props.userName,
|
||||
user_password: '',
|
||||
@ -214,7 +308,9 @@ class LoginPage extends React.Component {
|
||||
captcha_value: '',
|
||||
showPassword: false,
|
||||
disableInput: false,
|
||||
authFlow: props.flow,
|
||||
}
|
||||
|
||||
this.onHandleUserNameChange = this.onHandleUserNameChange.bind(this);
|
||||
this.onValidateEmail = this.onValidateEmail.bind(this);
|
||||
this.handleDelete = this.handleDelete.bind(this);
|
||||
@ -224,6 +320,29 @@ class LoginPage extends React.Component {
|
||||
this.shouldShowCaptcha = this.shouldShowCaptcha.bind(this);
|
||||
this.handleClickShowPassword = this.handleClickShowPassword.bind(this);
|
||||
this.handleMouseDownPassword = this.handleMouseDownPassword.bind(this);
|
||||
this.handleEmitOtpAction = this.handleEmitOtpAction.bind(this);
|
||||
}
|
||||
|
||||
handleEmitOtpAction(ev){
|
||||
ev.preventDefault();
|
||||
let user_fullname = this.state.user_fullname ? this.state.user_fullname : this.state.user_name;
|
||||
|
||||
emitOTP(this.state.user_name, this.props.token).then((payload) => {
|
||||
let {response} = payload;
|
||||
this.setState({...this.state,
|
||||
authFlow:"otp",
|
||||
errors: {
|
||||
email: "",
|
||||
password:"",
|
||||
},
|
||||
user_verified: true,
|
||||
user_fullname:user_fullname,
|
||||
});
|
||||
}, (error) => {
|
||||
let {response, status, message} = error;
|
||||
Swal('Oops...', 'Something went wrong!', 'error')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
shouldShowCaptcha() {
|
||||
@ -236,7 +355,11 @@ class LoginPage extends React.Component {
|
||||
|
||||
onAuthenticate(ev) {
|
||||
if (this.state.user_password == '') {
|
||||
this.setState({...this.state, errors: {...this.state.errors, password: 'Password is empty'}});
|
||||
let error = 'Password is empty';
|
||||
if(this.state.authFlow == 'OTP'){
|
||||
error = 'Verification Code is empty';
|
||||
}
|
||||
this.setState({...this.state, errors: {...this.state.errors, password: error}});
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
@ -276,9 +399,11 @@ class LoginPage extends React.Component {
|
||||
if (!emailValidator(this.state.user_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({...this.state, disableInput: true});
|
||||
verifyAccount(this.state.user_name).then((payload) => {
|
||||
let {response} = payload;
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
user_pic: response.pic,
|
||||
@ -291,10 +416,18 @@ class LoginPage extends React.Component {
|
||||
disableInput: false
|
||||
})
|
||||
}, (error) => {
|
||||
let {body} = error.res;
|
||||
let newErrors = {}
|
||||
|
||||
let {response, status, message} = error;
|
||||
|
||||
let newErrors = {};
|
||||
|
||||
newErrors['password'] = '';
|
||||
newErrors['email'] = "We could not find an Account with that email Address";
|
||||
|
||||
if(status == 429){
|
||||
newErrors['email'] = "Too many requests. Try it later.";
|
||||
}
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
user_pic: null,
|
||||
@ -308,7 +441,10 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
handleDelete() {
|
||||
this.setState({...this.state, user_name: null, user_pic: null, user_fullname: null, user_verified: false});
|
||||
this.setState({...this.state, user_name: null, user_pic: null, user_fullname: null, user_verified: false, authFlow:"password", errors: {
|
||||
email: "",
|
||||
password:"",
|
||||
},});
|
||||
}
|
||||
|
||||
handleClickShowPassword(ev) {
|
||||
@ -365,12 +501,13 @@ class LoginPage extends React.Component {
|
||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||
verifyEmailAction={this.props.verifyEmailAction}
|
||||
helpAction={this.props.helpAction}
|
||||
emitOtpAction={this.handleEmitOtpAction}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{this.state.user_verified &&
|
||||
{this.state.user_verified && this.state.authFlow == 'password' &&
|
||||
// proceed to ask for password ( 2nd step )
|
||||
<>
|
||||
<PasswordInputForm
|
||||
@ -394,9 +531,32 @@ class LoginPage extends React.Component {
|
||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||
verifyEmailAction={this.props.verifyEmailAction}
|
||||
helpAction={this.props.helpAction}
|
||||
emitOtpAction={this.handleEmitOtpAction}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{this.state.user_verified && this.state.authFlow == 'otp' &&
|
||||
// proceed to ask for password ( 2nd step )
|
||||
<>
|
||||
<OTPInputForm
|
||||
formAction={this.props.formAction}
|
||||
onAuthenticate={this.onAuthenticate}
|
||||
disableInput={this.state.disableInput}
|
||||
showPassword={this.state.showPassword}
|
||||
passwordValue={this.state.user_password}
|
||||
passwordError={this.state.errors.password}
|
||||
onUserPasswordChange={this.onUserPasswordChange}
|
||||
handleClickShowPassword={this.handleClickShowPassword}
|
||||
handleMouseDownPassword={this.handleMouseDownPassword}
|
||||
userNameValue={this.state.user_name}
|
||||
csrfToken={this.props.token}
|
||||
shouldShowCaptcha={this.shouldShowCaptcha}
|
||||
captchaPublicKey={this.props.captchaPublicKey}
|
||||
onChangeRecaptcha={this.onChangeRecaptcha}
|
||||
/>
|
||||
<OTPHelpLinks emitOtpAction={this.handleEmitOtpAction}/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ $base-color:#3fa2f7;
|
||||
color: $base-color;
|
||||
}
|
||||
|
||||
|
||||
.inner_container {
|
||||
display: flex;
|
||||
margin-top: 64px;
|
||||
@ -72,3 +73,8 @@ $base-color:#3fa2f7;
|
||||
.valid_user_name_chip{
|
||||
float: right;
|
||||
}
|
||||
|
||||
.otp_p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
@ -32,8 +32,10 @@
|
||||
appLogo: '{{ Config::get("app.logo_url") }}',
|
||||
formAction: '{{ URL::action("UserController@postLogin") }}',
|
||||
accountVerifyAction : '{{URL::action("UserController@getAccount")}}',
|
||||
emitOtpAction : '{{URL::action("UserController@emitOTP")}}',
|
||||
authError: authError,
|
||||
captchaPublicKey: '{{ Config::get("recaptcha.public_key") }}',
|
||||
flow: 'password',
|
||||
thirdPartyProviders: [
|
||||
@foreach($supported_providers as $provider => $label)
|
||||
{label: "{{$label}}", name:"{{$provider}}"},
|
||||
@ -63,9 +65,12 @@
|
||||
@if(Session::has('user_verified'))
|
||||
config.user_verified = {{Session::get('user_verified')}};
|
||||
@endif
|
||||
@if(Session::has('flow'))
|
||||
config.flow = '{{Session::get('flow')}}';
|
||||
@endif
|
||||
|
||||
window.VERIFY_ACCOUNT_ENDPOINT = config.accountVerifyAction;
|
||||
|
||||
window.EMIT_OTP_ENDPOINT = config.emitOtpAction;
|
||||
</script>
|
||||
{!! HTML::script('assets/login.js') !!}
|
||||
@append
|
201
resources/views/emails/oauth2_passwordless_otp.blade.php
Normal file
201
resources/views/emails/oauth2_passwordless_otp.blade.php
Normal file
@ -0,0 +1,201 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<!--
|
||||
** try it at https://mjml.io/try-it-live/
|
||||
<mjml>
|
||||
<mj-body background-color="#fafbfc">
|
||||
<mj-section padding-bottom="20px" padding-top="20px">
|
||||
<mj-column vertical-align="middle" width="100%">
|
||||
<mj-image align="center" padding="25px" src="{{Config::get('app.logo_url')}}" width="125px"></mj-image>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section background-color="#fff" padding-bottom="20px" padding-top="20px">
|
||||
<mj-column vertical-align="middle" width="100%">
|
||||
<mj-text align="center" font-size="16px" font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px"><span>Hello,</span></mj-text>
|
||||
<mj-text align="center" font-size="16px" font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px">Please use the verification code below on the {{Config::get('app.app_name')}} website:</mj-text>
|
||||
<mj-text align="center" font-size="24px" background-color="#20c997" font-weight="bold" font-family="open Sans Helvetica, Arial, sans-serif">{{$otp}}</mj-text>
|
||||
<mj-text align="center" font-size="16px" font-family="open Sans Helvetica, Arial, sans-serif"> Should be valid for {{$lifetime}} minutes.</mj-text>
|
||||
<mj-text align="center" font-size="16px" font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="16px">If you didn't request this, you can ignore this email or let us know.</mj-text>
|
||||
<mj-text align="center" font-size="16px" font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px">Thanks! <br />{{Config::get('app.tenant_name')}} Support Team</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="word-spacing:normal;background-color:#fafbfc;">
|
||||
<div style="background-color:#fafbfc;">
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:20px;padding-top:20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:125px;">
|
||||
<img height="auto" src="{{Config::get('app.logo_url')}}" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="125" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:20px;padding-top:20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:middle;width:600px;" ><![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:middle;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:middle;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;"><span>Hello,</span></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Please use the verification code below on the {{Config::get('app.app_name')}} website:</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:24px;font-weight:bold;line-height:1;text-align:center;color:#000000;">{{$otp}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Should be valid for {{$lifetime}} minutes.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:16px;padding-left:25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">If you didn't request this, you can ignore this email or let us know.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;padding-right:25px;padding-left:25px;word-break:break-word;">
|
||||
<div style="font-family:open Sans Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:center;color:#000000;">Thanks! <br />{{Config::get('app.tenant_name')}} Support Team</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -9,23 +9,23 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#"></a>
|
||||
<a class="navbar-brand" href="#" target="_self"></a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul id='main-menu' class="nav navbar-nav">
|
||||
<li id="profile"><a href='{!! URL::action("UserController@getProfile") !!}'>{{ __('Settings') }}</a></li>
|
||||
<li id="profile"><a target="_self" href='{!! URL::action("UserController@getProfile") !!}'>{{ __('Settings') }}</a></li>
|
||||
<li id="oauth2-console" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<a target="_self" href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
{{ __('OAUTH2 Console') }}<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href='{!!URL::action("AdminController@listOAuth2Clients")!!}'>{{ __('OAUTH2 Applications') }}</a></li>
|
||||
<li><a href='{!!URL::action("AdminController@editIssuedGrants")!!}'>{{ __('Issued OAUTH2 Grants') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listOAuth2Clients")!!}'>{{ __('OAUTH2 Applications') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@editIssuedGrants")!!}'>{{ __('Issued OAUTH2 Grants') }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@if(Auth::user()->isOpenIdServerAdmin() || Auth::user()->isOAuth2ServerAdmin() || Auth::user()->isSuperAdmin())
|
||||
<li id='server-admin' class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<a target="_self" href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
{{ __('Server Administration') }}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
@ -33,29 +33,29 @@
|
||||
@if(Auth::user()->isSuperAdmin() || Auth::user()->isOpenIdServerAdmin())
|
||||
<li class="dropdown-header">{{ __('Security') }}</li>
|
||||
@if(Auth::user()->isSuperAdmin())
|
||||
<li><a href='{!!URL::action("AdminController@listUsers")!!}'>{{ __('Users') }}</a></li>
|
||||
<li><a href='{!!URL::action("AdminController@listGroups")!!}'>{{ __('Groups') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listUsers")!!}'>{{ __('Users') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listGroups")!!}'>{{ __('Groups') }}</a></li>
|
||||
@endif
|
||||
<li><a href='{!!URL::action("AdminController@listBannedIPs")!!}'>{{ __('Banned IPs') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listBannedIPs")!!}'>{{ __('Banned IPs') }}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
@endif
|
||||
@if(Auth::user()->isOAuth2ServerAdmin())
|
||||
<li class="dropdown-header">{{ __('OAUTH2') }}</li>
|
||||
<li><a href='{!!URL::action("AdminController@listServerPrivateKeys")!!}'>{{ __('Private Keys') }}</a></li>
|
||||
<li><a href='{!!URL::action("AdminController@listResourceServers")!!}'>{{ __('Resource Servers') }}</a></li>
|
||||
<li><a href='{!!URL::action("AdminController@listApiScopeGroups")!!}'>{{ __('Api Scope Groups') }}</a></li>
|
||||
<li><a href='{!!URL::action("AdminController@listLockedClients")!!}'>{{ __('Locked Clients') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listServerPrivateKeys")!!}'>{{ __('Private Keys') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listResourceServers")!!}'>{{ __('Resource Servers') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listApiScopeGroups")!!}'>{{ __('Api Scope Groups') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listLockedClients")!!}'>{{ __('Locked Clients') }}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
@endif
|
||||
@if(Auth::user()->isOpenIdServerAdmin())
|
||||
<li class="dropdown-header">{{ __('Server') }}</li>
|
||||
<li><a href='{!!URL::action("AdminController@listServerConfig")!!}'>{{ __('Server Configuration') }}</a></li>
|
||||
<li><a target="_self" href='{!!URL::action("AdminController@listServerConfig")!!}'>{{ __('Server Configuration') }}</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
<li><a title="help" target="_blank" href="mailto:{!! Config::get("app.help_email") !!}">Help</a></li>
|
||||
<li><a href='{!! URL::action("UserController@logout") !!}'>Logout</a></li>
|
||||
<li><a target="_self" href='{!! URL::action("UserController@logout") !!}'>Logout</a></li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
|
@ -10,11 +10,41 @@
|
||||
id="pkce_enabled">
|
||||
Use PCKE?
|
||||
<span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||
aria-hidden="true" title="Use Proof Key for Code Exchange instead of a Client Secret ( Public Clients)"></span>
|
||||
aria-hidden="true" title="Use Proof Key for Code Exchange instead of a Client Secret (Public Clients)"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
@if ($client->otp_enabled)
|
||||
checked
|
||||
@endif
|
||||
id="otp_enabled">
|
||||
Use Passwordless?
|
||||
<span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||
aria-hidden="true" title="Use Passwordless Authentication"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group hidden otp_controls">
|
||||
<label for="otp_length">OTP Length
|
||||
<span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||
aria-hidden="true"
|
||||
title="One Time Password Length"></span></label>
|
||||
<input type="number" name="otp_length" class="form-control" id="otp_length"
|
||||
value="{!!$client->otp_length!!}">
|
||||
</div>
|
||||
<div class="form-group hidden otp_controls">
|
||||
<label for="otp_lifetime">OTP LifeTime (Seconds)
|
||||
<span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||
aria-hidden="true"
|
||||
title="One Time Password span lifetime in seconds"></span></label>
|
||||
<input type="number" name="otp_lifetime" class="form-control" id="otp_lifetime"
|
||||
value="{!!$client->otp_lifetime!!}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="default_max_age">Default Max. Age (optional) <span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||
aria-hidden="true"
|
||||
|
@ -44,7 +44,8 @@ Route::group(array('middleware' => ['ssl']), function () {
|
||||
|
||||
Route::group(array('prefix' => 'login'), function () {
|
||||
Route::get('', "UserController@getLogin");
|
||||
Route::get('account-verify', "UserController@getAccount");
|
||||
Route::get('account-verify', ['middleware' => ['csrf', 'throttle:account'], 'uses' => 'UserController@getAccount']);
|
||||
Route::post('otp', ['middleware' => ['csrf', 'throttle:otp'], 'uses' => 'UserController@emitOTP']);
|
||||
Route::post('', ['middleware' => 'csrf', 'uses' => 'UserController@postLogin']);
|
||||
Route::get('cancel', "UserController@cancelLogin");
|
||||
Route::group(array('prefix' => '{provider}'), function () {
|
||||
@ -56,7 +57,7 @@ Route::group(array('middleware' => ['ssl']), function () {
|
||||
// registration routes
|
||||
Route::group(array('prefix' => 'register'), function () {
|
||||
Route::get('', 'Auth\RegisterController@showRegistrationForm');
|
||||
Route::post('', ['middleware' => 'csrf', 'uses' => 'Auth\RegisterController@register']);
|
||||
Route::post('', ['middleware' => ['csrf'], 'uses' => 'Auth\RegisterController@register']);
|
||||
});
|
||||
|
||||
Route::group(array('prefix' => 'verification'), function () {
|
||||
@ -100,7 +101,8 @@ Route::group(['namespace' => 'OAuth2', 'middleware' => ['ssl']], function () {
|
||||
Route::get('/.well-known/openid-configuration', "OAuth2ProviderController@discovery");
|
||||
});
|
||||
|
||||
Route::group(['namespace' => 'OAuth2', 'prefix' => 'oauth2', 'middleware' => ['ssl']], function () {
|
||||
Route::group(['namespace' => 'OAuth2', 'prefix' => 'oauth2', 'middleware' => ['ssl','throttle:oauth2']], function () {
|
||||
|
||||
Route::get('/check-session', "OAuth2ProviderController@checkSessionIFrame");
|
||||
Route::get('/end-session', "OAuth2ProviderController@endSession");
|
||||
Route::post('/end-session', "OAuth2ProviderController@endSession");
|
||||
|
@ -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;
|
||||
|
557
tests/OIDCPasswordlessTest.php
Normal file
557
tests/OIDCPasswordlessTest.php
Normal file
@ -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");
|
||||
@ -11,6 +11,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
||||
@ -35,6 +36,9 @@ 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 +53,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();
|
||||
}
|
||||
@ -99,9 +103,9 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
'response_type' => 'code',
|
||||
'scope' => 'openid profile email',
|
||||
OAuth2Protocol::OAuth2Protocol_LoginHint => ' sebastian@tipit.net ',
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
|
||||
OAuth2Protocol::OAuth2Protocol_Display => OAuth2Protocol::OAuth2Protocol_Display_Native
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
|
||||
OAuth2Protocol::OAuth2Protocol_Display => OAuth2Protocol::OAuth2Protocol_Display_Native
|
||||
);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
@ -120,12 +124,12 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
// do login
|
||||
$response = $this->action('POST', "UserController@postLogin",
|
||||
array
|
||||
(
|
||||
[
|
||||
'username' => ' sebastian@tipit.net ',
|
||||
'password' => ' 1qaz2wsx ',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
'_token' => Session::token(),
|
||||
'flow' => 'password',
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertResponseStatus(302);
|
||||
@ -169,6 +173,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -254,6 +259,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -334,6 +340,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -447,6 +454,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -530,6 +538,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -554,7 +563,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$client_id = '%2E%2D%5F%7E87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
||||
$use_enc = true
|
||||
) {
|
||||
)
|
||||
{
|
||||
|
||||
|
||||
$params = array(
|
||||
@ -592,6 +602,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -701,7 +712,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
||||
$use_enc = true
|
||||
) {
|
||||
)
|
||||
{
|
||||
|
||||
|
||||
$params = array(
|
||||
@ -752,7 +764,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'_token' => Session::token()
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
|
||||
@ -857,7 +870,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
return $access_token;
|
||||
}
|
||||
|
||||
public function testGetRefreshTokenWithPromptSetToConsentLogin(){
|
||||
public function testGetRefreshTokenWithPromptSetToConsentLogin()
|
||||
{
|
||||
|
||||
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
|
||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
||||
@ -871,7 +885,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
OAuth2Protocol::OfflineAccess_Scope),
|
||||
OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce',
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s',OAuth2Protocol::OAuth2Protocol_Prompt_Login, OAuth2Protocol::OAuth2Protocol_Prompt_Consent),
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s', OAuth2Protocol::OAuth2Protocol_Prompt_Login, OAuth2Protocol::OAuth2Protocol_Prompt_Consent),
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200
|
||||
);
|
||||
|
||||
@ -898,6 +912,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1002,7 +1017,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
}
|
||||
|
||||
public function testFlowNativeDisplay(){
|
||||
public function testFlowNativeDisplay()
|
||||
{
|
||||
|
||||
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
|
||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
||||
@ -1014,9 +1030,9 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
'scope' => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope, OAuth2Protocol::OfflineAccess_Scope),
|
||||
OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce',
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s',OAuth2Protocol::OAuth2Protocol_Prompt_Login, OAuth2Protocol::OAuth2Protocol_Prompt_Consent),
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => sprintf('%s %s', OAuth2Protocol::OAuth2Protocol_Prompt_Login, OAuth2Protocol::OAuth2Protocol_Prompt_Consent),
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
|
||||
OAuth2Protocol::OAuth2Protocol_Display => OAuth2Protocol::OAuth2Protocol_Display_Native
|
||||
OAuth2Protocol::OAuth2Protocol_Display => OAuth2Protocol::OAuth2Protocol_Display_Native
|
||||
);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
@ -1031,7 +1047,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
$this->assertResponseStatus(412);
|
||||
|
||||
$json_response = json_decode($response->getContent(),true);
|
||||
$json_response = json_decode($response->getContent(), true);
|
||||
|
||||
// do login
|
||||
$response = $this->call($json_response['method'], $json_response['url'],
|
||||
@ -1039,6 +1055,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => $json_response['required_params_valid_values']["_token"]
|
||||
)
|
||||
);
|
||||
@ -1057,11 +1074,11 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
$this->assertResponseStatus(412);
|
||||
|
||||
$json_response = json_decode($response->getContent(),true);
|
||||
$json_response = json_decode($response->getContent(), true);
|
||||
|
||||
$response = $this->call($json_response['method'], $json_response['url'], array(
|
||||
'trust' => 'AllowOnce',
|
||||
'_token' => $json_response['required_params_valid_values']["_token"]
|
||||
'_token' => $json_response['required_params_valid_values']["_token"]
|
||||
));
|
||||
|
||||
$this->assertResponseStatus(302);
|
||||
@ -1117,21 +1134,21 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$this->assertTrue(!empty($id_token));
|
||||
}
|
||||
|
||||
public function testGetRefreshTokenFromNativeAppNTimes($n=5)
|
||||
public function testGetRefreshTokenFromNativeAppNTimes($n = 5)
|
||||
{
|
||||
$client_id = 'Jiz87D8/Vcvr6fvQbH4HyNgwKlfSyQ3x.android.openstack.client';
|
||||
$client_secret = '11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhg11c/6Y5N7kOtGKhgfdfdfdf';
|
||||
|
||||
$params_auth_code = array
|
||||
(
|
||||
'client_id' => $client_id,
|
||||
'redirect_uri' => 'androipapp://oidc_endpoint_callback',
|
||||
'response_type' => 'code',
|
||||
'scope' => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope, OAuth2Protocol::OfflineAccess_Scope),
|
||||
'client_id' => $client_id,
|
||||
'redirect_uri' => 'androipapp://oidc_endpoint_callback',
|
||||
'response_type' => 'code',
|
||||
'scope' => sprintf('%s profile email address %s', OAuth2Protocol::OpenIdConnect_Scope, OAuth2Protocol::OfflineAccess_Scope),
|
||||
OAuth2Protocol::OAuth2Protocol_LoginHint => 'sebastian@tipit.net',
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce',
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => 'test_nonce',
|
||||
OAuth2Protocol::OAuth2Protocol_Prompt => OAuth2Protocol::OAuth2Protocol_Prompt_Consent,
|
||||
OAuth2Protocol::OAuth2Protocol_MaxAge => 3200,
|
||||
);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth",
|
||||
@ -1157,6 +1174,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1280,9 +1298,9 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$this->assertTrue(!empty($output['code']));
|
||||
|
||||
$params = array(
|
||||
'code' => $output['code'],
|
||||
'code' => $output['code'],
|
||||
'redirect_uri' => 'androipapp://oidc_endpoint_callback',
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
@ -1309,7 +1327,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$this->assertTrue(!empty($refresh_token));
|
||||
$this->assertTrue(!empty($id_token));
|
||||
++$iteration;
|
||||
}while( $iteration < $n);
|
||||
} while ($iteration < $n);
|
||||
}
|
||||
|
||||
public function testTokenResponseModePost()
|
||||
@ -1354,6 +1372,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1500,6 +1519,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1638,6 +1658,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1816,6 +1837,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -1967,6 +1989,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2047,6 +2070,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2103,7 +2127,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
'client_id' => $client_id,
|
||||
'redirect_uri' => 'https://www.test.com/oauth2',
|
||||
'response_type' => OAuth2Protocol::OAuth2Protocol_ResponseType_IdToken,
|
||||
'scope' =>join(' ', [
|
||||
'scope' => join(' ', [
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
'profile',
|
||||
'email',
|
||||
@ -2131,6 +2155,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2177,7 +2202,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
sleep(10);
|
||||
|
||||
$params[OAuth2Protocol::OAuth2Protocol_Prompt] = OAuth2Protocol::OAuth2Protocol_Prompt_None;
|
||||
$params['scope'] =join(' ', [
|
||||
$params['scope'] = join(' ', [
|
||||
OAuth2Protocol::OpenIdConnect_Scope,
|
||||
'profile',
|
||||
'email',
|
||||
@ -2240,6 +2265,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2376,6 +2402,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2545,6 +2572,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2651,6 +2679,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2685,8 +2714,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
|
||||
$this->assertResponseStatus(302);
|
||||
|
||||
$url = $response->getTargetUrl();
|
||||
$comps = @parse_url($url);
|
||||
$url = $response->getTargetUrl();
|
||||
$comps = @parse_url($url);
|
||||
$fragment = $comps['fragment'];
|
||||
|
||||
$this->assertTrue(!empty($fragment));
|
||||
@ -2792,7 +2821,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
$key->setId('public_key_2');
|
||||
|
||||
$alg = new StringOrURI(JSONWebSignatureAndEncryptionAlgorithms::RS512);
|
||||
$jws = JWSFactory::build( new JWS_ParamsSpecification($key,$alg, $claim_set) );
|
||||
$jws = JWSFactory::build(new JWS_ParamsSpecification($key, $alg, $claim_set));
|
||||
// and sign with server private key
|
||||
$id_token_hint = $jws->toCompactSerialization();
|
||||
|
||||
@ -2871,6 +2900,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -2988,6 +3018,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
@ -3095,6 +3126,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
||||
(
|
||||
'username' => 'sebastian@tipit.net',
|
||||
'password' => '1qaz2wsx',
|
||||
'flow' => 'password',
|
||||
'_token' => Session::token()
|
||||
)
|
||||
);
|
||||
|
102
tests/OTPModelTest.php
Normal file
102
tests/OTPModelTest.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?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",
|
||||
OAuth2Protocol::OAuth2Protocol_Nonce => "123456"
|
||||
];
|
||||
|
||||
$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
Block a user