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
8d681c208f
@ -385,7 +385,7 @@ class AdminController extends Controller {
|
|||||||
if(is_null($endpoint)) return Response::view('errors.404', [], 404);
|
if(is_null($endpoint)) return Response::view('errors.404', [], 404);
|
||||||
$user = $this->auth_service->getCurrentUser();
|
$user = $this->auth_service->getCurrentUser();
|
||||||
$selected_scopes = [];
|
$selected_scopes = [];
|
||||||
$list = $endpoint->getScopes();
|
$list = $endpoint->getScope();
|
||||||
foreach($list as $selected_scope){
|
foreach($list as $selected_scope){
|
||||||
$selected_scopes[] = $selected_scope->getId();
|
$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_alg' => 'sometimes|required|encrypted_alg',
|
||||||
'id_token_encrypted_response_enc' => 'sometimes|required|encrypted_enc',
|
'id_token_encrypted_response_enc' => 'sometimes|required|encrypted_enc',
|
||||||
'admin_users' => 'nullable|int_array',
|
'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;
|
return $response;
|
||||||
} catch (OAuth2BaseException $ex1) {
|
} catch (OAuth2BaseException $ex1) {
|
||||||
|
$payload = [
|
||||||
|
'error' => $ex1->getError(),
|
||||||
|
'error_description' => $ex1->getMessage()
|
||||||
|
];
|
||||||
|
if (request()->isJson()) {
|
||||||
|
return Response::json($payload, 400);
|
||||||
|
}
|
||||||
return Response::view
|
return Response::view
|
||||||
(
|
(
|
||||||
'errors.400',
|
'errors.400',
|
||||||
[
|
$payload,
|
||||||
'error' => $ex1->getError(),
|
|
||||||
'error_description' => $ex1->getMessage()
|
|
||||||
],
|
|
||||||
400
|
400
|
||||||
);
|
);
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
Log::error($ex);
|
Log::error($ex);
|
||||||
|
$payload = [
|
||||||
|
'error' => "Bad Request",
|
||||||
|
'error_description' => "Generic Error"
|
||||||
|
];
|
||||||
|
if (request()->isJson()) {
|
||||||
|
return Response::json($payload, 400);
|
||||||
|
}
|
||||||
return Response::view
|
return Response::view
|
||||||
(
|
(
|
||||||
'errors.400',
|
'errors.400',
|
||||||
[
|
$payload,
|
||||||
'error' => "Bad Request",
|
|
||||||
'error_description' => "Generic Error"
|
|
||||||
],
|
|
||||||
400
|
400
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ use Illuminate\Support\Facades\Validator;
|
|||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use models\exceptions\EntityNotFoundException;
|
use models\exceptions\EntityNotFoundException;
|
||||||
use models\exceptions\ValidationException;
|
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\IApiScopeRepository;
|
||||||
use OAuth2\Repositories\IClientRepository;
|
use OAuth2\Repositories\IClientRepository;
|
||||||
use OpenId\Services\IUserService;
|
use OpenId\Services\IUserService;
|
||||||
@ -232,7 +236,8 @@ final class UserController extends OpenIdController
|
|||||||
/**
|
/**
|
||||||
* @return \Illuminate\Http\JsonResponse|mixed
|
* @return \Illuminate\Http\JsonResponse|mixed
|
||||||
*/
|
*/
|
||||||
public function getAccount(){
|
public function getAccount()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$email = Request::input("email", "");
|
$email = Request::input("email", "");
|
||||||
if (empty($email)) {
|
if (empty($email)) {
|
||||||
@ -250,16 +255,83 @@ final class UserController extends OpenIdController
|
|||||||
'full_name' => $user->getFullName()
|
'full_name' => $user->getFullName()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
} catch (ValidationException $ex) {
|
||||||
catch (ValidationException $ex){
|
|
||||||
Log::warning($ex);
|
Log::warning($ex);
|
||||||
return $this->error412($ex->getMessages());
|
return $this->error412($ex->getMessages());
|
||||||
}
|
} catch (EntityNotFoundException $ex) {
|
||||||
catch (EntityNotFoundException $ex){
|
|
||||||
Log::warning($ex);
|
Log::warning($ex);
|
||||||
return $this->error404();
|
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);
|
Log::error($ex);
|
||||||
return $this->error500($ex);
|
return $this->error500($ex);
|
||||||
}
|
}
|
||||||
@ -271,6 +343,7 @@ final class UserController extends OpenIdController
|
|||||||
$login_attempts = 0;
|
$login_attempts = 0;
|
||||||
$username = '';
|
$username = '';
|
||||||
$user = null;
|
$user = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -287,29 +360,64 @@ final class UserController extends OpenIdController
|
|||||||
$rules = [
|
$rules = [
|
||||||
'username' => 'required|email',
|
'username' => 'required|email',
|
||||||
'password' => 'required',
|
'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';
|
$rules['g-recaptcha-response'] = 'required|recaptcha';
|
||||||
}
|
}
|
||||||
// Create a new validator instance.
|
// Create a new validator instance.
|
||||||
$validator = Validator::make($data, $rules);
|
$validator = Validator::make($data, $rules);
|
||||||
|
|
||||||
if ($validator->passes()) {
|
if ($validator->passes()) {
|
||||||
|
|
||||||
$username = $data['username'];
|
$username = $data['username'];
|
||||||
$password = $data['password'];
|
$password = $data['password'];
|
||||||
|
$flow = $data['flow'];
|
||||||
$remember = Request::input("remember");
|
$remember = Request::input("remember");
|
||||||
$remember = !is_null($remember);
|
$remember = !is_null($remember);
|
||||||
|
$connection = $data['connection'] ?? null;
|
||||||
|
|
||||||
if ($this->auth_service->login($username, $password, $remember))
|
try {
|
||||||
{
|
if ($flow == "password" && $this->auth_service->login($username, $password, $remember)) {
|
||||||
return $this->login_strategy->postLogin();
|
return $this->login_strategy->postLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
//failed login attempt...
|
if ($flow == "otp") {
|
||||||
$user = $this->auth_service->getUserByUsername($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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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)) {
|
if (!is_null($user)) {
|
||||||
$login_attempts = $user->getLoginFailedAttempt();
|
$login_attempts = $user->getLoginFailedAttempt();
|
||||||
}
|
}
|
||||||
@ -319,20 +427,23 @@ final class UserController extends OpenIdController
|
|||||||
[
|
[
|
||||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||||
'login_attempts' => $login_attempts,
|
'login_attempts' => $login_attempts,
|
||||||
'error_message' => "We are sorry, your username or password does not match an existing record.",
|
'error_message' => $ex->getMessage(),
|
||||||
'user_fullname' => $user->getFullName(),
|
'user_fullname' => !is_null($user) ? $user->getFullName() : "",
|
||||||
'user_pic' => $user->getPic(),
|
'user_pic' => !is_null($user) ? $user->getPic(): "",
|
||||||
'user_verified' => true,
|
'user_verified' => true,
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
|
'flow' => $flow
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// validator errors
|
// validator errors
|
||||||
$response_data = [
|
$response_data = [
|
||||||
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
'max_login_attempts_2_show_captcha' => $max_login_attempts_2_show_captcha,
|
||||||
'login_attempts' => $login_attempts,
|
'login_attempts' => $login_attempts,
|
||||||
'validator' => $validator
|
'validator' => $validator,
|
||||||
];
|
];
|
||||||
|
|
||||||
if(!is_null($user)){
|
if(!is_null($user)){
|
||||||
@ -389,7 +500,6 @@ final class UserController extends OpenIdController
|
|||||||
400
|
400
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->consent_strategy->getConsent();
|
return $this->consent_strategy->getConsent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,7 +573,6 @@ final class UserController extends OpenIdController
|
|||||||
$pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url;
|
$pic_url = str_contains($pic_url, 'http') ? $pic_url : $assets_url . $pic_url;
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
|
|
||||||
'show_fullname' => $user->getShowProfileFullName(),
|
'show_fullname' => $user->getShowProfileFullName(),
|
||||||
'username' => $user->getFullName(),
|
'username' => $user->getFullName(),
|
||||||
'show_email' => $user->getShowProfileEmail(),
|
'show_email' => $user->getShowProfileEmail(),
|
||||||
|
@ -67,7 +67,7 @@ class Kernel extends HttpKernel
|
|||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
'ssl' => \App\Http\Middleware\SSLMiddleware::class,
|
'ssl' => \App\Http\Middleware\SSLMiddleware::class,
|
||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::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,
|
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
'oauth2.endpoint' => \App\Http\Middleware\OAuth2BearerAccessTokenRequestValidator::class,
|
'oauth2.endpoint' => \App\Http\Middleware\OAuth2BearerAccessTokenRequestValidator::class,
|
||||||
'oauth2.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdmin::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()
|
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';
|
$view = 'emails.welcome_new_user_email';
|
||||||
|
|
||||||
if(Config::get("app.tenant_name") == 'FNTECH') {
|
if(Config::get("app.tenant_name") == 'FNTECH') {
|
||||||
$view = 'emails.welcome_new_user_email_fn';
|
$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));
|
Log::debug(sprintf("WelcomeNewUserEmail::build to %s", $this->user_email));
|
||||||
return $this->from(Config::get("mail.from"))
|
return $this->from(Config::get("mail.from"))
|
||||||
->to($this->user_email)
|
->to($this->user_email)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
use App\libs\Utils\URLUtils;
|
use App\libs\Utils\URLUtils;
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Doctrine\Common\Collections\Criteria;
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use jwa\cryptographic_algorithms\ContentEncryptionAlgorithms_Registry;
|
use jwa\cryptographic_algorithms\ContentEncryptionAlgorithms_Registry;
|
||||||
use jwa\cryptographic_algorithms\DigitalSignatures_MACs_Registry;
|
use jwa\cryptographic_algorithms\DigitalSignatures_MACs_Registry;
|
||||||
@ -90,6 +91,24 @@ class Client extends BaseEntity implements IClient
|
|||||||
*/
|
*/
|
||||||
private $pkce_enabled;
|
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")
|
* @ORM\Column(name="locked", type="boolean")
|
||||||
* @var bool
|
* @var bool
|
||||||
@ -343,13 +362,13 @@ class Client extends BaseEntity implements IClient
|
|||||||
private $admin_users;
|
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
|
* @var ArrayCollection
|
||||||
*/
|
*/
|
||||||
private $refresh_tokens;
|
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
|
* @var ArrayCollection
|
||||||
*/
|
*/
|
||||||
private $access_tokens;
|
private $access_tokens;
|
||||||
@ -364,6 +383,12 @@ class Client extends BaseEntity implements IClient
|
|||||||
*/
|
*/
|
||||||
private $scopes;
|
private $scopes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="Models\OAuth2\OAuth2OTP", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||||
|
* @var ArrayCollection
|
||||||
|
*/
|
||||||
|
private $otp_grants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client constructor.
|
* Client constructor.
|
||||||
*/
|
*/
|
||||||
@ -374,6 +399,7 @@ class Client extends BaseEntity implements IClient
|
|||||||
$this->access_tokens = new ArrayCollection();
|
$this->access_tokens = new ArrayCollection();
|
||||||
$this->refresh_tokens = new ArrayCollection();
|
$this->refresh_tokens = new ArrayCollection();
|
||||||
$this->admin_users = new ArrayCollection();
|
$this->admin_users = new ArrayCollection();
|
||||||
|
$this->otp_grants = new ArrayCollection();
|
||||||
$this->scopes = new ArrayCollection();
|
$this->scopes = new ArrayCollection();
|
||||||
$this->locked = false;
|
$this->locked = false;
|
||||||
$this->active = 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_basis = 0;
|
||||||
$this->max_refresh_token_issuance_qty = 0;
|
$this->max_refresh_token_issuance_qty = 0;
|
||||||
$this->pkce_enabled = false;
|
$this->pkce_enabled = false;
|
||||||
|
$this->otp_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static $valid_app_types = [
|
public static $valid_app_types = [
|
||||||
@ -443,7 +470,9 @@ class Client extends BaseEntity implements IClient
|
|||||||
$this->getApplicationType() == IClient::ApplicationType_Native ||
|
$this->getApplicationType() == IClient::ApplicationType_Native ||
|
||||||
$this->getApplicationType() == IClient::ApplicationType_Web_App ||
|
$this->getApplicationType() == IClient::ApplicationType_Web_App ||
|
||||||
// PCKE
|
// 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;
|
$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 App\libs\Utils\URLUtils;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Models\OAuth2\Client;
|
use Models\OAuth2\Client;
|
||||||
use Models\OAuth2\ResourceServer;
|
use Models\OAuth2\ResourceServer;
|
||||||
use OAuth2\Models\IClient;
|
use OAuth2\Models\IClient;
|
||||||
@ -199,14 +200,32 @@ final class ClientFactory
|
|||||||
$client->disablePCKE();
|
$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);
|
$scope_repository = App::make(IApiScopeRepository::class);
|
||||||
//add default scopes
|
//add default scopes
|
||||||
foreach ($scope_repository->getDefaults() as $default_scope) {
|
foreach ($scope_repository->getDefaults() as $default_scope) {
|
||||||
|
Log::debug(sprintf("ClientFactory::populate processing scope %s", $default_scope->getName()));
|
||||||
if
|
if
|
||||||
(
|
(
|
||||||
$default_scope->getName() === OAuth2Protocol::OfflineAccess_Scope
|
$default_scope->getName() === OAuth2Protocol::OfflineAccess_Scope
|
||||||
&& !$client->canRequestRefreshTokens()
|
&& !$client->canRequestRefreshTokens()
|
||||||
) {
|
) {
|
||||||
|
Log::debug(sprintf("ClientFactory::populate skipping scope %s", $default_scope->getName()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$client->addScope($default_scope);
|
$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.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use App\Models\Utils\BaseEntity;
|
use App\Models\Utils\BaseEntity;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
|
||||||
use Doctrine\ORM\Mapping AS ORM;
|
use Doctrine\ORM\Mapping AS ORM;
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass="App\Repositories\DoctrineOAuth2TrailExceptionRepository")
|
* @ORM\Entity(repositoryClass="App\Repositories\DoctrineOAuth2TrailExceptionRepository")
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
use Illuminate\Routing\Router;
|
use Illuminate\Routing\Router;
|
||||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class RouteServiceProvider
|
* Class RouteServiceProvider
|
||||||
* @package App\Providers
|
* @package App\Providers
|
||||||
@ -38,6 +42,27 @@ final class RouteServiceProvider extends ServiceProvider
|
|||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
parent::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) {
|
||||||
|
return Limit::perMinute(25)->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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use App\libs\Auth\Models\SpamEstimatorFeed;
|
use App\libs\Auth\Models\SpamEstimatorFeed;
|
||||||
use App\libs\Auth\Models\UserRegistrationRequest;
|
use App\libs\Auth\Models\UserRegistrationRequest;
|
||||||
use App\libs\Auth\Repositories\IBannedIPRepository;
|
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\IUserPasswordResetRequestRepository;
|
||||||
use App\libs\Auth\Repositories\IUserRegistrationRequestRepository;
|
use App\libs\Auth\Repositories\IUserRegistrationRequestRepository;
|
||||||
use App\libs\Auth\Repositories\IWhiteListedIPRepository;
|
use App\libs\Auth\Repositories\IWhiteListedIPRepository;
|
||||||
|
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||||
use App\libs\OAuth2\Repositories\IOAuth2TrailExceptionRepository;
|
use App\libs\OAuth2\Repositories\IOAuth2TrailExceptionRepository;
|
||||||
use App\Models\Repositories\IDisqusSSOProfileRepository;
|
use App\Models\Repositories\IDisqusSSOProfileRepository;
|
||||||
use App\Models\Repositories\IRocketChatSSOProfileRepository;
|
use App\Models\Repositories\IRocketChatSSOProfileRepository;
|
||||||
@ -43,6 +45,7 @@ use Models\OAuth2\ApiScope;
|
|||||||
use Models\OAuth2\ApiScopeGroup;
|
use Models\OAuth2\ApiScopeGroup;
|
||||||
use Models\OAuth2\Client;
|
use Models\OAuth2\Client;
|
||||||
use Models\OAuth2\ClientPublicKey;
|
use Models\OAuth2\ClientPublicKey;
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
use Models\OAuth2\OAuth2TrailException;
|
use Models\OAuth2\OAuth2TrailException;
|
||||||
use Models\OAuth2\RefreshToken;
|
use Models\OAuth2\RefreshToken;
|
||||||
use Models\OAuth2\ResourceServer;
|
use Models\OAuth2\ResourceServer;
|
||||||
@ -67,16 +70,19 @@ use OAuth2\Repositories\IServerPrivateKeyRepository;
|
|||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
use OpenId\Repositories\IOpenIdAssociationRepository;
|
use OpenId\Repositories\IOpenIdAssociationRepository;
|
||||||
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class RepositoriesProvider
|
* Class RepositoriesProvider
|
||||||
* @package Repositories
|
* @package Repositories
|
||||||
*/
|
*/
|
||||||
final class RepositoriesProvider extends ServiceProvider implements DeferrableProvider
|
final class RepositoriesProvider extends ServiceProvider implements DeferrableProvider
|
||||||
{
|
{
|
||||||
public function boot(){
|
public function boot()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register(){
|
public function register()
|
||||||
|
{
|
||||||
|
|
||||||
App::singleton(IGroupRepository::class,
|
App::singleton(IGroupRepository::class,
|
||||||
function () {
|
function () {
|
||||||
@ -87,8 +93,7 @@ final class RepositoriesProvider extends ServiceProvider implements DeferrablePr
|
|||||||
App::singleton(IUserPasswordResetRequestRepository::class,
|
App::singleton(IUserPasswordResetRequestRepository::class,
|
||||||
function () {
|
function () {
|
||||||
return EntityManager::getRepository(UserPasswordResetRequest::class);
|
return EntityManager::getRepository(UserPasswordResetRequest::class);
|
||||||
})
|
});
|
||||||
;
|
|
||||||
|
|
||||||
App::singleton(IServerExtensionRepository::class,
|
App::singleton(IServerExtensionRepository::class,
|
||||||
function () {
|
function () {
|
||||||
@ -250,32 +255,45 @@ final class RepositoriesProvider extends ServiceProvider implements DeferrablePr
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
App::singleton(
|
||||||
|
IOAuth2OTPRepository::class,
|
||||||
|
function () {
|
||||||
|
return EntityManager::getRepository(OAuth2OTP::class);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provides()
|
public function provides()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
IServerConfigurationRepository::class,
|
|
||||||
IGroupRepository::class,
|
IGroupRepository::class,
|
||||||
IOpenIdAssociationRepository::class,
|
IUserPasswordResetRequestRepository::class,
|
||||||
|
IServerExtensionRepository::class,
|
||||||
IOpenIdTrustedSiteRepository::class,
|
IOpenIdTrustedSiteRepository::class,
|
||||||
|
IOpenIdAssociationRepository::class,
|
||||||
|
IServerConfigurationRepository::class,
|
||||||
|
IUserExceptionTrailRepository::class,
|
||||||
|
IBannedIPRepository::class,
|
||||||
|
IWhiteListedIPRepository::class,
|
||||||
IUserRepository::class,
|
IUserRepository::class,
|
||||||
|
IResourceServerRepository::class,
|
||||||
|
IApiRepository::class,
|
||||||
|
IApiEndpointRepository::class,
|
||||||
|
IClientRepository::class,
|
||||||
|
IAccessTokenRepository::class,
|
||||||
|
IRefreshTokenRepository::class,
|
||||||
|
IApiScopeRepository::class,
|
||||||
|
IApiScopeGroupRepository::class,
|
||||||
|
IOAuth2TrailExceptionRepository::class,
|
||||||
IClientPublicKeyRepository::class,
|
IClientPublicKeyRepository::class,
|
||||||
IServerPrivateKeyRepository::class,
|
IServerPrivateKeyRepository::class,
|
||||||
IClientRepository::class,
|
|
||||||
IApiScopeGroupRepository::class,
|
|
||||||
IApiEndpointRepository::class,
|
|
||||||
IRefreshTokenRepository::class,
|
|
||||||
IAccessTokenRepository::class,
|
|
||||||
IApiScopeRepository::class,
|
|
||||||
IApiRepository::class,
|
|
||||||
IResourceServerRepository::class,
|
|
||||||
IWhiteListedIPRepository::class,
|
|
||||||
IUserRegistrationRequestRepository::class,
|
IUserRegistrationRequestRepository::class,
|
||||||
ISpamEstimatorFeedRepository::class,
|
ISpamEstimatorFeedRepository::class,
|
||||||
IDisqusSSOProfileRepository::class,
|
IDisqusSSOProfileRepository::class,
|
||||||
IRocketChatSSOProfileRepository::class,
|
IRocketChatSSOProfileRepository::class,
|
||||||
IStreamChatSSOProfileRepository::class,
|
IStreamChatSSOProfileRepository::class,
|
||||||
|
IOAuth2OTPRepository::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -128,6 +128,7 @@ final class UserService extends AbstractService implements IUserService
|
|||||||
if(count($default_groups) > 0){
|
if(count($default_groups) > 0){
|
||||||
$payload['groups'] = $default_groups;
|
$payload['groups'] = $default_groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = UserFactory::build($payload);
|
$user = UserFactory::build($payload);
|
||||||
|
|
||||||
$this->user_repository->add($user);
|
$this->user_repository->add($user);
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
use App\Http\Utils\IUserIPHelperProvider;
|
use App\Http\Utils\IUserIPHelperProvider;
|
||||||
|
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
|
||||||
|
use App\Services\Auth\IUserService;
|
||||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use OAuth2\Services\AccessTokenGenerator;
|
|
||||||
use OAuth2\Services\AuthorizationCodeGenerator;
|
|
||||||
use OAuth2\Services\IApiScopeService;
|
use OAuth2\Services\IApiScopeService;
|
||||||
use OAuth2\Services\OAuth2ServiceCatalog;
|
use OAuth2\Services\OAuth2ServiceCatalog;
|
||||||
use OAuth2\Services\RefreshTokenGenerator;
|
use Utils\Services\IdentifierGenerator;
|
||||||
use Utils\Services\UtilsServiceCatalog;
|
use Utils\Services\UtilsServiceCatalog;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
/**
|
/**
|
||||||
@ -70,9 +70,7 @@ final class OAuth2ServiceProvider extends ServiceProvider implements DeferrableP
|
|||||||
App::make(UtilsServiceCatalog::CacheService),
|
App::make(UtilsServiceCatalog::CacheService),
|
||||||
App::make(UtilsServiceCatalog::AuthenticationService),
|
App::make(UtilsServiceCatalog::AuthenticationService),
|
||||||
App::make(OAuth2ServiceCatalog::UserConsentService),
|
App::make(OAuth2ServiceCatalog::UserConsentService),
|
||||||
new AuthorizationCodeGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
App::make(IdentifierGenerator::class),
|
||||||
new AccessTokenGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
|
||||||
new RefreshTokenGenerator(App::make(UtilsServiceCatalog::CacheService)),
|
|
||||||
App::make(\OAuth2\Repositories\IServerPrivateKeyRepository::class),
|
App::make(\OAuth2\Repositories\IServerPrivateKeyRepository::class),
|
||||||
new HttpIClientJWKSetReader,
|
new HttpIClientJWKSetReader,
|
||||||
App::make(OAuth2ServiceCatalog::SecurityContextService),
|
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\IAccessTokenRepository::class),
|
||||||
App::make(\OAuth2\Repositories\IRefreshTokenRepository::class),
|
App::make(\OAuth2\Repositories\IRefreshTokenRepository::class),
|
||||||
App::make(\OAuth2\Repositories\IResourceServerRepository::class),
|
App::make(\OAuth2\Repositories\IResourceServerRepository::class),
|
||||||
|
App::make(IOAuth2OTPRepository::class),
|
||||||
App::make(IUserIPHelperProvider::class),
|
App::make(IUserIPHelperProvider::class),
|
||||||
App::make(IApiScopeService::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()
|
public function provides()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
IdentifierGenerator::class,
|
||||||
\OAuth2\IResourceServerContext::class,
|
\OAuth2\IResourceServerContext::class,
|
||||||
OAuth2ServiceCatalog::ClientCredentialGenerator,
|
OAuth2ServiceCatalog::ClientCredentialGenerator,
|
||||||
OAuth2ServiceCatalog::ClientService,
|
OAuth2ServiceCatalog::ClientService,
|
||||||
|
@ -14,7 +14,13 @@
|
|||||||
|
|
||||||
use App\Http\Utils\IUserIPHelperProvider;
|
use App\Http\Utils\IUserIPHelperProvider;
|
||||||
use App\libs\Auth\Models\IGroupSlugs;
|
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\AbstractService;
|
||||||
|
use App\Services\Auth\IUserService;
|
||||||
|
use App\Strategies\OTP\OTPChannelStrategyFactory;
|
||||||
|
use App\Strategies\OTP\OTPTypeBuilderStrategyFactory;
|
||||||
|
use Auth\Exceptions\AuthenticationException;
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -23,6 +29,9 @@ use jwt\IBasicJWT;
|
|||||||
use jwt\impl\JWTClaimSet;
|
use jwt\impl\JWTClaimSet;
|
||||||
use jwt\JWTClaim;
|
use jwt\JWTClaim;
|
||||||
use models\exceptions\ValidationException;
|
use models\exceptions\ValidationException;
|
||||||
|
use Models\OAuth2\Client;
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
|
use OAuth2\Exceptions\InvalidOTPException;
|
||||||
use OAuth2\Models\AccessToken;
|
use OAuth2\Models\AccessToken;
|
||||||
use Models\OAuth2\AccessToken as AccessTokenDB;
|
use Models\OAuth2\AccessToken as AccessTokenDB;
|
||||||
use Models\OAuth2\RefreshToken as RefreshTokenDB;
|
use Models\OAuth2\RefreshToken as RefreshTokenDB;
|
||||||
@ -48,6 +57,7 @@ use OAuth2\Repositories\IRefreshTokenRepository;
|
|||||||
use OAuth2\Repositories\IResourceServerRepository;
|
use OAuth2\Repositories\IResourceServerRepository;
|
||||||
use OAuth2\Requests\OAuth2AuthenticationRequest;
|
use OAuth2\Requests\OAuth2AuthenticationRequest;
|
||||||
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
||||||
|
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||||
use OAuth2\Services\IApiScopeService;
|
use OAuth2\Services\IApiScopeService;
|
||||||
use OAuth2\Services\ITokenService;
|
use OAuth2\Services\ITokenService;
|
||||||
use OAuth2\OAuth2Protocol;
|
use OAuth2\OAuth2Protocol;
|
||||||
@ -66,7 +76,7 @@ use Utils\Exceptions\UnacquiredLockException;
|
|||||||
use utils\json_types\JsonValue;
|
use utils\json_types\JsonValue;
|
||||||
use utils\json_types\NumericDate;
|
use utils\json_types\NumericDate;
|
||||||
use utils\json_types\StringOrURI;
|
use utils\json_types\StringOrURI;
|
||||||
use Utils\Model\Identifier;
|
use Utils\Model\AbstractIdentifier;
|
||||||
use Utils\Services\IAuthService;
|
use Utils\Services\IAuthService;
|
||||||
use Utils\Services\ICacheService;
|
use Utils\Services\ICacheService;
|
||||||
use Utils\Services\IdentifierGenerator;
|
use Utils\Services\IdentifierGenerator;
|
||||||
@ -100,6 +110,10 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
* @var IClientService
|
* @var IClientService
|
||||||
*/
|
*/
|
||||||
private $client_service;
|
private $client_service;
|
||||||
|
/**
|
||||||
|
* @var IUserService
|
||||||
|
*/
|
||||||
|
private $user_service;
|
||||||
/**
|
/**
|
||||||
* @var ILockManagerService
|
* @var ILockManagerService
|
||||||
*/
|
*/
|
||||||
@ -123,15 +137,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
/**
|
/**
|
||||||
* @var IdentifierGenerator
|
* @var IdentifierGenerator
|
||||||
*/
|
*/
|
||||||
private $auth_code_generator;
|
private $identifier_generator;
|
||||||
/**
|
|
||||||
* @var IdentifierGenerator
|
|
||||||
*/
|
|
||||||
private $access_token_generator;
|
|
||||||
/**
|
|
||||||
* @var IdentifierGenerator
|
|
||||||
*/
|
|
||||||
private $refresh_token_generator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var IServerPrivateKeyRepository
|
* @var IServerPrivateKeyRepository
|
||||||
@ -187,6 +193,34 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
*/
|
*/
|
||||||
private $ip_helper;
|
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
|
public function __construct
|
||||||
(
|
(
|
||||||
IClientService $client_service,
|
IClientService $client_service,
|
||||||
@ -195,9 +229,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
ICacheService $cache_service,
|
ICacheService $cache_service,
|
||||||
IAuthService $auth_service,
|
IAuthService $auth_service,
|
||||||
IUserConsentService $user_consent_service,
|
IUserConsentService $user_consent_service,
|
||||||
IdentifierGenerator $auth_code_generator,
|
IdentifierGenerator $identifier_generator,
|
||||||
IdentifierGenerator $access_token_generator,
|
|
||||||
IdentifierGenerator $refresh_token_generator,
|
|
||||||
IServerPrivateKeyRepository $server_private_key_repository,
|
IServerPrivateKeyRepository $server_private_key_repository,
|
||||||
IClientJWKSetReader $jwk_set_reader_service,
|
IClientJWKSetReader $jwk_set_reader_service,
|
||||||
ISecurityContextService $security_context_service,
|
ISecurityContextService $security_context_service,
|
||||||
@ -207,8 +239,10 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
IAccessTokenRepository $access_token_repository,
|
IAccessTokenRepository $access_token_repository,
|
||||||
IRefreshTokenRepository $refresh_token_repository,
|
IRefreshTokenRepository $refresh_token_repository,
|
||||||
IResourceServerRepository $resource_server_repository,
|
IResourceServerRepository $resource_server_repository,
|
||||||
|
IOAuth2OTPRepository $otp_repository,
|
||||||
IUserIPHelperProvider $ip_helper,
|
IUserIPHelperProvider $ip_helper,
|
||||||
IApiScopeService $scope_service,
|
IApiScopeService $scope_service,
|
||||||
|
IUserService $user_service,
|
||||||
ITransactionService $tx_service
|
ITransactionService $tx_service
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -220,9 +254,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
$this->cache_service = $cache_service;
|
$this->cache_service = $cache_service;
|
||||||
$this->auth_service = $auth_service;
|
$this->auth_service = $auth_service;
|
||||||
$this->user_consent_service = $user_consent_service;
|
$this->user_consent_service = $user_consent_service;
|
||||||
$this->auth_code_generator = $auth_code_generator;
|
$this->identifier_generator = $identifier_generator;
|
||||||
$this->access_token_generator = $access_token_generator;
|
|
||||||
$this->refresh_token_generator = $refresh_token_generator;
|
|
||||||
$this->server_private_key_repository = $server_private_key_repository;
|
$this->server_private_key_repository = $server_private_key_repository;
|
||||||
$this->jwk_set_reader_service = $jwk_set_reader_service;
|
$this->jwk_set_reader_service = $jwk_set_reader_service;
|
||||||
$this->security_context_service = $security_context_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->resource_server_repository = $resource_server_repository;
|
||||||
$this->ip_helper = $ip_helper;
|
$this->ip_helper = $ip_helper;
|
||||||
$this->scope_service = $scope_service;
|
$this->scope_service = $scope_service;
|
||||||
|
$this->user_service = $user_service;
|
||||||
|
$this->otp_repository = $otp_repository;
|
||||||
|
|
||||||
Event::listen('oauth2.client.delete', function ($client_id) {
|
Event::listen('oauth2.client.delete', function ($client_id) {
|
||||||
$this->revokeClientRelatedTokens($client_id);
|
$this->revokeClientRelatedTokens($client_id);
|
||||||
@ -248,13 +282,13 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
* Creates a brand new authorization code
|
* Creates a brand new authorization code
|
||||||
* @param OAuth2AuthorizationRequest $request
|
* @param OAuth2AuthorizationRequest $request
|
||||||
* @param bool $has_previous_user_consent
|
* @param bool $has_previous_user_consent
|
||||||
* @return Identifier
|
* @return AbstractIdentifier
|
||||||
*/
|
*/
|
||||||
public function createAuthorizationCode
|
public function createAuthorizationCode
|
||||||
(
|
(
|
||||||
OAuth2AuthorizationRequest $request,
|
OAuth2AuthorizationRequest $request,
|
||||||
bool $has_previous_user_consent = false
|
bool $has_previous_user_consent = false
|
||||||
): Identifier
|
): AbstractIdentifier
|
||||||
{
|
{
|
||||||
|
|
||||||
$user = $this->auth_service->getCurrentUser();
|
$user = $this->auth_service->getCurrentUser();
|
||||||
@ -276,7 +310,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
$prompt = $request->getPrompt(true);
|
$prompt = $request->getPrompt(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$code = $this->auth_code_generator->generate
|
$code = $this->identifier_generator->generate
|
||||||
(
|
(
|
||||||
AuthorizationCode::create
|
AuthorizationCode::create
|
||||||
(
|
(
|
||||||
@ -354,7 +388,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
public function createAccessToken(AuthorizationCode $auth_code, $redirect_uri = null)
|
public function createAccessToken(AuthorizationCode $auth_code, $redirect_uri = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
$access_token = $this->access_token_generator->generate
|
$access_token = $this->identifier_generator->generate
|
||||||
(
|
(
|
||||||
AccessToken::create
|
AccessToken::create
|
||||||
(
|
(
|
||||||
@ -462,7 +496,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
public function createAccessTokenFromParams($client_id, $scope, $audience, $user_id = null)
|
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,
|
$scope,
|
||||||
$client_id,
|
$client_id,
|
||||||
@ -559,7 +593,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
}
|
}
|
||||||
|
|
||||||
//create new access token
|
//create new access token
|
||||||
$access_token = $this->access_token_generator->generate
|
$access_token = $this->identifier_generator->generate
|
||||||
(
|
(
|
||||||
AccessToken::createFromRefreshToken
|
AccessToken::createFromRefreshToken
|
||||||
(
|
(
|
||||||
@ -830,7 +864,7 @@ final class TokenService extends AbstractService implements ITokenService
|
|||||||
*/
|
*/
|
||||||
public function createRefreshToken(AccessToken &$access_token, $refresh_cache = false)
|
public function createRefreshToken(AccessToken &$access_token, $refresh_cache = false)
|
||||||
{
|
{
|
||||||
$refresh_token = $this->refresh_token_generator->generate(
|
$refresh_token = $this->identifier_generator->generate(
|
||||||
RefreshToken::create(
|
RefreshToken::create(
|
||||||
$access_token,
|
$access_token,
|
||||||
$this->configuration_service->getConfigValue('OAuth2.RefreshToken.Lifetime')
|
$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);
|
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\Contracts\Support\DeferrableProvider;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use OpenId\Services\NonceUniqueIdentifierGenerator;
|
|
||||||
use OpenId\Services\OpenIdServiceCatalog;
|
use OpenId\Services\OpenIdServiceCatalog;
|
||||||
|
use Utils\Services\IdentifierGenerator;
|
||||||
use Utils\Services\UtilsServiceCatalog;
|
use Utils\Services\UtilsServiceCatalog;
|
||||||
/**
|
/**
|
||||||
* Class OpenIdProvider
|
* Class OpenIdProvider
|
||||||
@ -44,7 +44,7 @@ final class OpenIdProvider extends ServiceProvider implements DeferrableProvider
|
|||||||
App::make(UtilsServiceCatalog::LockManagerService),
|
App::make(UtilsServiceCatalog::LockManagerService),
|
||||||
App::make(UtilsServiceCatalog::CacheService),
|
App::make(UtilsServiceCatalog::CacheService),
|
||||||
App::make(UtilsServiceCatalog::ServerConfigurationService),
|
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\Repositories\IServerConfigurationRepository;
|
||||||
use App\Services\Utils\DoctrineTransactionService;
|
use App\Services\Utils\DoctrineTransactionService;
|
||||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||||
|
use Utils\Services\IdentifierGenerator;
|
||||||
|
use Utils\Services\UniqueIdentifierGenerator;
|
||||||
use Utils\Services\UtilsServiceCatalog;
|
use Utils\Services\UtilsServiceCatalog;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
@ -31,6 +33,8 @@ final class UtilsProvider extends ServiceProvider implements DeferrableProvider
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
|
App::singleton(IdentifierGenerator::class, UniqueIdentifierGenerator::class);
|
||||||
|
|
||||||
App::singleton(UtilsServiceCatalog::CacheService, RedisCacheService::class);
|
App::singleton(UtilsServiceCatalog::CacheService, RedisCacheService::class);
|
||||||
App::singleton(UtilsServiceCatalog::TransactionService, function(){
|
App::singleton(UtilsServiceCatalog::TransactionService, function(){
|
||||||
return new DoctrineTransactionService(BaseEntity::EntityManager);
|
return new DoctrineTransactionService(BaseEntity::EntityManager);
|
||||||
@ -55,12 +59,14 @@ final class UtilsProvider extends ServiceProvider implements DeferrableProvider
|
|||||||
return new ExternalUrlService();
|
return new ExternalUrlService();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provides()
|
public function provides()
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
|
IdentifierGenerator::class,
|
||||||
UtilsServiceCatalog::CacheService,
|
UtilsServiceCatalog::CacheService,
|
||||||
UtilsServiceCatalog::TransactionService,
|
UtilsServiceCatalog::TransactionService,
|
||||||
UtilsServiceCatalog::LogService,
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use App\libs\OAuth2\Exceptions\ReloadSessionException;
|
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 Auth\Repositories\IUserRepository;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Cookie;
|
use Illuminate\Support\Facades\Cookie;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Models\OAuth2\Client;
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
|
use OAuth2\Exceptions\InvalidOTPException;
|
||||||
use OAuth2\Models\IClient;
|
use OAuth2\Models\IClient;
|
||||||
use OAuth2\Services\IPrincipalService;
|
use OAuth2\Services\IPrincipalService;
|
||||||
use OpenId\Models\IOpenIdUser;
|
|
||||||
use OpenId\Services\IUserService;
|
use OpenId\Services\IUserService;
|
||||||
|
use App\Services\Auth\IUserService as IAuthUserService;
|
||||||
use utils\Base64UrlRepresentation;
|
use utils\Base64UrlRepresentation;
|
||||||
|
use Utils\Db\ITransactionService;
|
||||||
use Utils\Services\IAuthService;
|
use Utils\Services\IAuthService;
|
||||||
use Utils\Services\ICacheService;
|
use Utils\Services\ICacheService;
|
||||||
use jwe\compression_algorithms\CompressionAlgorithms_Registry;
|
use jwe\compression_algorithms\CompressionAlgorithms_Registry;
|
||||||
use jwe\compression_algorithms\CompressionAlgorithmsNames;
|
use jwe\compression_algorithms\CompressionAlgorithmsNames;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AuthService
|
* Class AuthService
|
||||||
* @package Auth
|
* @package Auth
|
||||||
*/
|
*/
|
||||||
final class AuthService implements IAuthService
|
final class AuthService extends AbstractService implements IAuthService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var IPrincipalService
|
* @var IPrincipalService
|
||||||
@ -53,25 +62,44 @@ final class AuthService implements IAuthService
|
|||||||
*/
|
*/
|
||||||
private $user_repository;
|
private $user_repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var IAuthUserService
|
||||||
|
*/
|
||||||
|
private $auth_user_service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var IOAuth2OTPRepository
|
||||||
|
*/
|
||||||
|
private $otp_repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuthService constructor.
|
* AuthService constructor.
|
||||||
* @param IUserRepository $user_repository
|
* @param IUserRepository $user_repository
|
||||||
|
* @param IOAuth2OTPRepository $otp_repository
|
||||||
* @param IPrincipalService $principal_service
|
* @param IPrincipalService $principal_service
|
||||||
* @param IUserService $user_service
|
* @param IUserService $user_service
|
||||||
* @param ICacheService $cache_service
|
* @param ICacheService $cache_service
|
||||||
|
* @param IAuthUserService $auth_user_service
|
||||||
|
* @param ITransactionService $tx_service
|
||||||
*/
|
*/
|
||||||
public function __construct
|
public function __construct
|
||||||
(
|
(
|
||||||
IUserRepository $user_repository,
|
IUserRepository $user_repository,
|
||||||
|
IOAuth2OTPRepository $otp_repository,
|
||||||
IPrincipalService $principal_service,
|
IPrincipalService $principal_service,
|
||||||
IUserService $user_service,
|
IUserService $user_service,
|
||||||
ICacheService $cache_service
|
ICacheService $cache_service,
|
||||||
|
IAuthUserService $auth_user_service,
|
||||||
|
ITransactionService $tx_service
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
parent::__construct($tx_service);
|
||||||
$this->user_repository = $user_repository;
|
$this->user_repository = $user_repository;
|
||||||
$this->principal_service = $principal_service;
|
$this->principal_service = $principal_service;
|
||||||
$this->user_service = $user_service;
|
$this->user_service = $user_service;
|
||||||
$this->cache_service = $cache_service;
|
$this->cache_service = $cache_service;
|
||||||
|
$this->auth_user_service = $auth_user_service;
|
||||||
|
$this->otp_repository = $otp_repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,15 +122,18 @@ final class AuthService implements IAuthService
|
|||||||
* @param string $username
|
* @param string $username
|
||||||
* @param string $password
|
* @param string $password
|
||||||
* @param bool $remember_me
|
* @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");
|
Log::debug("AuthService::login");
|
||||||
$res = Auth::attempt(['username' => $username, 'password' => $password], $remember_me);
|
|
||||||
|
|
||||||
if ($res)
|
$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.");
|
||||||
|
}
|
||||||
|
|
||||||
Log::debug("AuthService::login: clearing principal");
|
Log::debug("AuthService::login: clearing principal");
|
||||||
$this->principal_service->clear();
|
$this->principal_service->clear();
|
||||||
$this->principal_service->register
|
$this->principal_service->register
|
||||||
@ -110,9 +141,77 @@ final class AuthService implements IAuthService
|
|||||||
$this->getCurrentUser()->getId(),
|
$this->getCurrentUser()->getId(),
|
||||||
time()
|
time()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
/**
|
||||||
|
* @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()
|
public function logout()
|
||||||
@ -140,8 +239,7 @@ final class AuthService implements IAuthService
|
|||||||
*/
|
*/
|
||||||
public function getUserAuthorizationResponse()
|
public function getUserAuthorizationResponse()
|
||||||
{
|
{
|
||||||
if (Session::has("openid.authorization.response"))
|
if (Session::has("openid.authorization.response")) {
|
||||||
{
|
|
||||||
$value = Session::get("openid.authorization.response");
|
$value = Session::get("openid.authorization.response");
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
@ -153,8 +251,7 @@ final class AuthService implements IAuthService
|
|||||||
|
|
||||||
public function clearUserAuthorizationResponse()
|
public function clearUserAuthorizationResponse()
|
||||||
{
|
{
|
||||||
if (Session::has("openid.authorization.response"))
|
if (Session::has("openid.authorization.response")) {
|
||||||
{
|
|
||||||
Session::remove("openid.authorization.response");
|
Session::remove("openid.authorization.response");
|
||||||
Session::save();
|
Session::save();
|
||||||
}
|
}
|
||||||
@ -212,8 +309,7 @@ final class AuthService implements IAuthService
|
|||||||
|
|
||||||
public function clearUserAuthenticationResponse()
|
public function clearUserAuthenticationResponse()
|
||||||
{
|
{
|
||||||
if (Session::has("openstackid.authentication.response"))
|
if (Session::has("openstackid.authentication.response")) {
|
||||||
{
|
|
||||||
Session::remove("openstackid.authentication.response");
|
Session::remove("openstackid.authentication.response");
|
||||||
Session::save();
|
Session::save();
|
||||||
}
|
}
|
||||||
@ -299,8 +395,7 @@ final class AuthService implements IAuthService
|
|||||||
|
|
||||||
$rps = $zlib->compress($rps);
|
$rps = $zlib->compress($rps);
|
||||||
$rps = $this->encrypt($rps);
|
$rps = $this->encrypt($rps);
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch(Exception $ex){
|
|
||||||
Log::warning($ex);
|
Log::warning($ex);
|
||||||
$rps = "";
|
$rps = "";
|
||||||
}
|
}
|
||||||
@ -327,8 +422,7 @@ final class AuthService implements IAuthService
|
|||||||
$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);
|
$zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib);
|
||||||
|
|
||||||
if(!empty($rps))
|
if (!empty($rps)) {
|
||||||
{
|
|
||||||
$rps = $this->decrypt($rps);
|
$rps = $this->decrypt($rps);
|
||||||
$rps = $zlib->uncompress($rps);
|
$rps = $zlib->uncompress($rps);
|
||||||
return explode('|', $rps);
|
return explode('|', $rps);
|
||||||
@ -357,8 +451,7 @@ final class AuthService implements IAuthService
|
|||||||
|
|
||||||
Session::setId(Crypt::decrypt($session_id));
|
Session::setId(Crypt::decrypt($session_id));
|
||||||
Session::start();
|
Session::start();
|
||||||
if(!Auth::check())
|
if (!Auth::check()) {
|
||||||
{
|
|
||||||
$user_id = $this->principal_service->get()->getUserId();
|
$user_id = $this->principal_service->get()->getUserId();
|
||||||
Log::debug(sprintf("AuthService::reloadSession user_id %s", $user_id));
|
Log::debug(sprintf("AuthService::reloadSession user_id %s", $user_id));
|
||||||
$user = $this->getUserById($user_id);
|
$user = $this->getUserById($user_id);
|
||||||
@ -373,7 +466,8 @@ final class AuthService implements IAuthService
|
|||||||
* @param int $id_token_lifetime
|
* @param int $id_token_lifetime
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function generateJTI(string $client_id, int $id_token_lifetime):string {
|
public function generateJTI(string $client_id, int $id_token_lifetime): string
|
||||||
|
{
|
||||||
$session_id = Crypt::encrypt(Session::getId());
|
$session_id = Crypt::encrypt(Session::getId());
|
||||||
$encoder = new Base64UrlRepresentation();
|
$encoder = new Base64UrlRepresentation();
|
||||||
$jti = $encoder->encode(hash('sha512', $session_id . $client_id, true));
|
$jti = $encoder->encode(hash('sha512', $session_id . $client_id, true));
|
||||||
@ -383,9 +477,10 @@ final class AuthService implements IAuthService
|
|||||||
return $jti;
|
return $jti;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function invalidateSession(): void
|
||||||
public function invalidateSession():void {
|
{
|
||||||
$session_id = Crypt::encrypt(Session::getId());
|
$session_id = Crypt::encrypt(Session::getId());
|
||||||
$this->cache_service->addSingleValue($session_id . "invalid", $session_id);
|
$this->cache_service->addSingleValue($session_id . "invalid", $session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -21,7 +21,6 @@ class AuthenticationException extends Exception
|
|||||||
|
|
||||||
public function __construct($message = "")
|
public function __construct($message = "")
|
||||||
{
|
{
|
||||||
$message = "Authentication Exception : " . $message;
|
|
||||||
parent::__construct($message, 0, null);
|
parent::__construct($message, 0, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -474,14 +474,18 @@ class User extends BaseEntity
|
|||||||
return $this->identifier;
|
return $this->identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmail()
|
public function getEmail():string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
public function getFullName(): ?string
|
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()
|
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
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,24 +11,18 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use OAuth2\OAuth2Protocol;
|
use OAuth2\OAuth2Protocol;
|
||||||
use Utils\Model\Identifier;
|
|
||||||
use Utils\Services\UniqueIdentifierGenerator;
|
|
||||||
use Zend\Math\Rand;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OAuth2TokenGenerator
|
* Class InvalidRedeemOTPException
|
||||||
* @package OAuth2\Services
|
* @package OAuth2\Exceptions
|
||||||
*/
|
*/
|
||||||
class OAuth2TokenGenerator extends UniqueIdentifierGenerator
|
final class InvalidRedeemOTPException extends OAuth2BaseException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param Identifier $identifier
|
* @return string
|
||||||
* @return Identifier
|
|
||||||
*/
|
*/
|
||||||
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||||
use OAuth2\Exceptions\InvalidAuthenticationRequestException;
|
use OAuth2\Exceptions\InvalidAuthenticationRequestException;
|
||||||
use OAuth2\Exceptions\InvalidAuthorizationRequestException;
|
use OAuth2\Exceptions\InvalidAuthorizationRequestException;
|
||||||
use OAuth2\OAuth2Protocol;
|
use OAuth2\OAuth2Protocol;
|
||||||
@ -33,8 +35,14 @@ final class OAuth2AuthorizationRequestFactory
|
|||||||
|
|
||||||
$auth_request = new OAuth2AuthorizationRequest($msg);
|
$auth_request = new OAuth2AuthorizationRequest($msg);
|
||||||
$scope = $auth_request->getScope();
|
$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) ) {
|
if(!is_null($scope) && str_contains($scope, OAuth2Protocol::OpenIdConnect_Scope) ) {
|
||||||
$auth_request = new OAuth2AuthenticationRequest($auth_request);
|
return new OAuth2AuthenticationRequest($auth_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $auth_request;
|
return $auth_request;
|
||||||
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
|
use OAuth2\OAuth2Protocol;
|
||||||
|
use Zend\Math\Rand;
|
||||||
/**
|
/**
|
||||||
* Class AccessToken
|
* Class AccessToken
|
||||||
* @see http://tools.ietf.org/html/rfc6749#section-1.4
|
* @see http://tools.ietf.org/html/rfc6749#section-1.4
|
||||||
@ -23,6 +26,11 @@ class AccessToken extends Token {
|
|||||||
*/
|
*/
|
||||||
private $auth_code;
|
private $auth_code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var OAuth2OTP
|
||||||
|
*/
|
||||||
|
private $otp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var RefreshToken
|
* @var RefreshToken
|
||||||
*/
|
*/
|
||||||
@ -53,6 +61,25 @@ class AccessToken extends Token {
|
|||||||
return $instance;
|
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){
|
public static function createFromParams($scope, $client_id, $audience,$user_id,$lifetime){
|
||||||
$instance = new self();
|
$instance = new self();
|
||||||
$instance->scope = $scope;
|
$instance->scope = $scope;
|
||||||
@ -130,7 +157,7 @@ class AccessToken extends Token {
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType():string
|
||||||
{
|
{
|
||||||
return 'access_token';
|
return 'access_token';
|
||||||
}
|
}
|
||||||
@ -142,4 +169,29 @@ class AccessToken extends Token {
|
|||||||
{
|
{
|
||||||
return [];
|
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 Utils\IPHelper;
|
||||||
use OAuth2\OAuth2Protocol;
|
use OAuth2\OAuth2Protocol;
|
||||||
|
use Zend\Math\Rand;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AuthorizationCode
|
* Class AuthorizationCode
|
||||||
@ -313,7 +314,7 @@ class AuthorizationCode extends Token
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType():string
|
||||||
{
|
{
|
||||||
return 'auth_code';
|
return 'auth_code';
|
||||||
}
|
}
|
||||||
@ -384,4 +385,9 @@ class AuthorizationCode extends Token
|
|||||||
return $this->code_challenge_method;
|
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
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isPKCEEnabled():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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
use OAuth2\OAuth2Protocol;
|
||||||
use Utils\IPHelper;
|
use Utils\IPHelper;
|
||||||
|
use Zend\Math\Rand;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class RefreshToken
|
* Class RefreshToken
|
||||||
* @see http://tools.ietf.org/html/rfc6749#section-1.5
|
* @see http://tools.ietf.org/html/rfc6749#section-1.5
|
||||||
@ -81,7 +85,7 @@ class RefreshToken extends Token {
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType():string
|
||||||
{
|
{
|
||||||
return 'refresh_token';
|
return 'refresh_token';
|
||||||
}
|
}
|
||||||
@ -93,4 +97,10 @@ class RefreshToken extends Token {
|
|||||||
{
|
{
|
||||||
return [];
|
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 DateTime;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use Utils\IPHelper;
|
use Utils\IPHelper;
|
||||||
use Utils\Model\Identifier;
|
use Utils\Model\AbstractIdentifier;
|
||||||
/**
|
/**
|
||||||
* Class Token
|
* Class Token
|
||||||
* Defines the common behavior for all emitted tokens
|
* Defines the common behavior for all emitted tokens
|
||||||
* @package OAuth2\Models
|
* @package OAuth2\Models
|
||||||
*/
|
*/
|
||||||
abstract class Token extends Identifier
|
abstract class Token extends AbstractIdentifier
|
||||||
{
|
{
|
||||||
|
|
||||||
const DefaultByteLength = 32;
|
const DefaultByteLength = 32;
|
||||||
@ -54,6 +54,9 @@ abstract class Token extends Identifier
|
|||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $is_hashed;
|
protected $is_hashed;
|
||||||
|
/**
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
protected $user_id;
|
protected $user_id;
|
||||||
|
|
||||||
public function __construct($len = self::DefaultByteLength)
|
public function __construct($len = self::DefaultByteLength)
|
||||||
@ -141,4 +144,5 @@ abstract class Token extends Identifier
|
|||||||
|
|
||||||
|
|
||||||
public abstract function fromJSON($json);
|
public abstract function fromJSON($json);
|
||||||
|
|
||||||
}
|
}
|
@ -11,6 +11,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use App\Http\Utils\UserIPHelperProvider;
|
use App\Http\Utils\UserIPHelperProvider;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@ -38,6 +39,7 @@ use OAuth2\GrantTypes\AuthorizationCodeGrantType;
|
|||||||
use OAuth2\GrantTypes\ClientCredentialsGrantType;
|
use OAuth2\GrantTypes\ClientCredentialsGrantType;
|
||||||
use OAuth2\GrantTypes\HybridGrantType;
|
use OAuth2\GrantTypes\HybridGrantType;
|
||||||
use OAuth2\GrantTypes\ImplicitGrantType;
|
use OAuth2\GrantTypes\ImplicitGrantType;
|
||||||
|
use OAuth2\GrantTypes\PasswordlessGrantType;
|
||||||
use OAuth2\GrantTypes\RefreshBearerTokenGrantType;
|
use OAuth2\GrantTypes\RefreshBearerTokenGrantType;
|
||||||
use OAuth2\Models\IClient;
|
use OAuth2\Models\IClient;
|
||||||
use OAuth2\Repositories\IClientRepository;
|
use OAuth2\Repositories\IClientRepository;
|
||||||
@ -64,6 +66,7 @@ use utils\factories\BasicJWTFactory;
|
|||||||
use Utils\Services\IAuthService;
|
use Utils\Services\IAuthService;
|
||||||
use Utils\Services\ICheckPointService;
|
use Utils\Services\ICheckPointService;
|
||||||
use Utils\Services\ILogService;
|
use Utils\Services\ILogService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OAuth2Protocol
|
* Class OAuth2Protocol
|
||||||
* Implementation of @see http://tools.ietf.org/html/rfc6749
|
* Implementation of @see http://tools.ietf.org/html/rfc6749
|
||||||
@ -80,6 +83,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
const OAuth2Protocol_ResponseType_Delimiter = ' ';
|
const OAuth2Protocol_ResponseType_Delimiter = ' ';
|
||||||
|
|
||||||
const OAuth2Protocol_GrantType_AuthCode = 'authorization_code';
|
const OAuth2Protocol_GrantType_AuthCode = 'authorization_code';
|
||||||
|
const OAuth2Protocol_GrantType_Passwordless = 'passwordless';
|
||||||
const OAuth2Protocol_GrantType_Implicit = 'implicit';
|
const OAuth2Protocol_GrantType_Implicit = 'implicit';
|
||||||
const OAuth2Protocol_GrantType_Hybrid = 'hybrid';
|
const OAuth2Protocol_GrantType_Hybrid = 'hybrid';
|
||||||
|
|
||||||
@ -88,6 +92,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token';
|
const OAuth2Protocol_GrantType_RefreshToken = 'refresh_token';
|
||||||
|
|
||||||
const OAuth2Protocol_ResponseType_Code = 'code';
|
const OAuth2Protocol_ResponseType_Code = 'code';
|
||||||
|
const OAuth2Protocol_ResponseType_OTP = 'otp';
|
||||||
const OAuth2Protocol_ResponseType_Token = 'token';
|
const OAuth2Protocol_ResponseType_Token = 'token';
|
||||||
const OAuth2Protocol_ResponseType_IdToken = 'id_token';
|
const OAuth2Protocol_ResponseType_IdToken = 'id_token';
|
||||||
const OAuth2Protocol_ResponseType_None = 'none';
|
const OAuth2Protocol_ResponseType_None = 'none';
|
||||||
@ -213,6 +218,28 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
const OAuth2Protocol_Audience = 'audience';
|
const OAuth2Protocol_Audience = 'audience';
|
||||||
const OAuth2Protocol_State = 'state';
|
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
|
* @see http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions
|
||||||
* In OpenID Connect, the session at the RP typically starts when the RP validates the End-User's ID Token. Refer
|
* In OpenID Connect, the session at the RP typically starts when the RP validates the End-User's ID Token. Refer
|
||||||
@ -363,14 +390,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
static public function getValidResponseTypes($flow = 'all')
|
static public function getValidResponseTypes($flow = 'all')
|
||||||
{
|
{
|
||||||
$code_flow = array
|
$code_flow = [
|
||||||
(
|
|
||||||
//OAuth2 / OIDC
|
//OAuth2 / OIDC
|
||||||
array
|
[
|
||||||
(
|
|
||||||
self::OAuth2Protocol_ResponseType_Code
|
self::OAuth2Protocol_ResponseType_Code
|
||||||
)
|
]
|
||||||
);
|
];
|
||||||
|
|
||||||
$implicit_flow = array
|
$implicit_flow = array
|
||||||
(
|
(
|
||||||
@ -419,6 +444,13 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$hybrid_flow
|
$hybrid_flow
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless)
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
self::OAuth2Protocol_ResponseType_OTP
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)
|
if ($flow === OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode)
|
||||||
return $code_flow;
|
return $code_flow;
|
||||||
|
|
||||||
@ -448,21 +480,20 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
(
|
(
|
||||||
!in_array
|
!in_array
|
||||||
(
|
(
|
||||||
$flow, array
|
$flow, [
|
||||||
(
|
|
||||||
OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||||
OAuth2Protocol::OAuth2Protocol_GrantType_Implicit,
|
OAuth2Protocol::OAuth2Protocol_GrantType_Implicit,
|
||||||
OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid,
|
OAuth2Protocol::OAuth2Protocol_GrantType_Hybrid,
|
||||||
|
OAuth2Protocol::OAuth2Protocol_GrantType_Passwordless,
|
||||||
'all'
|
'all'
|
||||||
)
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$flow_response_types = self::getValidResponseTypes($flow);
|
$flow_response_types = self::getValidResponseTypes($flow);
|
||||||
|
|
||||||
foreach($flow_response_types as $rt)
|
foreach ($flow_response_types as $rt) {
|
||||||
{
|
|
||||||
if (count($rt) !== count($response_type)) continue;
|
if (count($rt) !== count($response_type)) continue;
|
||||||
$diff = array_diff($rt, $response_type);
|
$diff = array_diff($rt, $response_type);
|
||||||
if (count($diff) === 0) return true;
|
if (count($diff) === 0) return true;
|
||||||
@ -554,6 +585,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
const OAuth2Protocol_ErrorUri = 'error_uri';
|
const OAuth2Protocol_ErrorUri = 'error_uri';
|
||||||
const OAuth2Protocol_Error_InvalidRequest = 'invalid_request';
|
const OAuth2Protocol_Error_InvalidRequest = 'invalid_request';
|
||||||
const OAuth2Protocol_Error_UnauthorizedClient = 'unauthorized_client';
|
const OAuth2Protocol_Error_UnauthorizedClient = 'unauthorized_client';
|
||||||
|
const OAuth2Protocol_Error_InvalidOTP = 'invalid_otp';
|
||||||
const OAuth2Protocol_Error_RedirectUriMisMatch = 'redirect_uri_mismatch';
|
const OAuth2Protocol_Error_RedirectUriMisMatch = 'redirect_uri_mismatch';
|
||||||
const OAuth2Protocol_Error_AccessDenied = 'access_denied';
|
const OAuth2Protocol_Error_AccessDenied = 'access_denied';
|
||||||
const OAuth2Protocol_Error_UnsupportedResponseType = 'unsupported_response_type';
|
const OAuth2Protocol_Error_UnsupportedResponseType = 'unsupported_response_type';
|
||||||
@ -859,6 +891,23 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$this->oidc_provider_configuration_service = $oidc_provider_configuration_service;
|
$this->oidc_provider_configuration_service = $oidc_provider_configuration_service;
|
||||||
$this->memento_service = $memento_service;
|
$this->memento_service = $memento_service;
|
||||||
|
|
||||||
|
$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
|
$authorization_code_grant_type = new AuthorizationCodeGrantType
|
||||||
(
|
(
|
||||||
$scope_service,
|
$scope_service,
|
||||||
@ -927,6 +976,8 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$log_service
|
$log_service
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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[$authorization_code_grant_type->getType()] = $authorization_code_grant_type;
|
||||||
$this->grant_types[$implicit_grant_type->getType()] = $implicit_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[$refresh_bearer_token_grant_type->getType()] = $refresh_bearer_token_grant_type;
|
||||||
@ -964,14 +1015,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
public function authorize(OAuth2Request $request = null)
|
public function authorize(OAuth2Request $request = null)
|
||||||
{
|
{
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
$this->last_request = $request;
|
$this->last_request = $request;
|
||||||
|
|
||||||
if (is_null($this->last_request)) throw new InvalidOAuth2Request;
|
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 ....
|
// then check if we have a memento ....
|
||||||
if (!$this->memento_service->exists())
|
if (!$this->memento_service->exists())
|
||||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||||
@ -986,15 +1035,11 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
|
|
||||||
}
|
}
|
||||||
return $this->authorize_endpoint->handle($this->last_request);
|
return $this->authorize_endpoint->handle($this->last_request);
|
||||||
}
|
} catch (UriNotAllowedException $ex1) {
|
||||||
catch (UriNotAllowedException $ex1)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex1);
|
$this->log_service->warning($ex1);
|
||||||
$this->checkpoint_service->trackException($ex1);
|
$this->checkpoint_service->trackException($ex1);
|
||||||
throw $ex1;
|
throw $ex1;
|
||||||
}
|
} catch (OAuth2BaseException $ex2) {
|
||||||
catch(OAuth2BaseException $ex2)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex2);
|
$this->log_service->warning($ex2);
|
||||||
$this->checkpoint_service->trackException($ex2);
|
$this->checkpoint_service->trackException($ex2);
|
||||||
|
|
||||||
@ -1010,8 +1055,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$ex2->getMessage(),
|
$ex2->getMessage(),
|
||||||
$redirect_uri
|
$redirect_uri
|
||||||
);
|
);
|
||||||
}
|
} catch (AbsentClientException $ex3) {
|
||||||
catch (AbsentClientException $ex3){
|
|
||||||
$this->log_service->warning($ex3);
|
$this->log_service->warning($ex3);
|
||||||
$this->checkpoint_service->trackException($ex3);
|
$this->checkpoint_service->trackException($ex3);
|
||||||
|
|
||||||
@ -1026,8 +1070,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$ex3->getMessage(),
|
$ex3->getMessage(),
|
||||||
$redirect_uri
|
$redirect_uri
|
||||||
);
|
);
|
||||||
}
|
} catch (AbsentCurrentUserException $ex4) {
|
||||||
catch (AbsentCurrentUserException $ex4){
|
|
||||||
$this->log_service->warning($ex4);
|
$this->log_service->warning($ex4);
|
||||||
$this->checkpoint_service->trackException($ex4);
|
$this->checkpoint_service->trackException($ex4);
|
||||||
|
|
||||||
@ -1042,9 +1085,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$ex4->getMessage(),
|
$ex4->getMessage(),
|
||||||
$redirect_uri
|
$redirect_uri
|
||||||
);
|
);
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch (Exception $ex)
|
|
||||||
{
|
|
||||||
$this->log_service->error($ex);
|
$this->log_service->error($ex);
|
||||||
$this->checkpoint_service->trackException($ex);
|
$this->checkpoint_service->trackException($ex);
|
||||||
|
|
||||||
@ -1068,8 +1109,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
public function token(OAuth2Request $request = null)
|
public function token(OAuth2Request $request = null)
|
||||||
{
|
{
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
$this->last_request = $request;
|
$this->last_request = $request;
|
||||||
|
|
||||||
if (is_null($this->last_request))
|
if (is_null($this->last_request))
|
||||||
@ -1079,16 +1119,12 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||||
|
|
||||||
return $this->token_endpoint->handle($this->last_request);
|
return $this->token_endpoint->handle($this->last_request);
|
||||||
}
|
} catch (OAuth2BaseException $ex1) {
|
||||||
catch(OAuth2BaseException $ex1)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex1);
|
$this->log_service->warning($ex1);
|
||||||
$this->checkpoint_service->trackException($ex1);
|
$this->checkpoint_service->trackException($ex1);
|
||||||
|
|
||||||
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch (Exception $ex)
|
|
||||||
{
|
|
||||||
$this->log_service->error($ex);
|
$this->log_service->error($ex);
|
||||||
$this->checkpoint_service->trackException($ex);
|
$this->checkpoint_service->trackException($ex);
|
||||||
|
|
||||||
@ -1106,7 +1142,8 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
* @param OAuth2Request $request
|
* @param OAuth2Request $request
|
||||||
* @return OAuth2Response
|
* @return OAuth2Response
|
||||||
*/
|
*/
|
||||||
public function revoke(OAuth2Request $request = null){
|
public function revoke(OAuth2Request $request = null)
|
||||||
|
{
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->last_request = $request;
|
$this->last_request = $request;
|
||||||
@ -1118,8 +1155,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||||
|
|
||||||
return $this->revoke_endpoint->handle($this->last_request);
|
return $this->revoke_endpoint->handle($this->last_request);
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch (Exception $ex) {
|
|
||||||
$this->log_service->error($ex);
|
$this->log_service->error($ex);
|
||||||
$this->checkpoint_service->trackException($ex);
|
$this->checkpoint_service->trackException($ex);
|
||||||
//simple say "OK" and be on our way ...
|
//simple say "OK" and be on our way ...
|
||||||
@ -1135,8 +1171,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
public function introspection(OAuth2Request $request = null)
|
public function introspection(OAuth2Request $request = null)
|
||||||
{
|
{
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
$this->last_request = $request;
|
$this->last_request = $request;
|
||||||
|
|
||||||
if (is_null($this->last_request))
|
if (is_null($this->last_request))
|
||||||
@ -1146,20 +1181,14 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
throw new InvalidOAuth2Request($this->last_request->getLastValidationError());
|
||||||
|
|
||||||
return $this->introspection_endpoint->handle($this->last_request);
|
return $this->introspection_endpoint->handle($this->last_request);
|
||||||
}
|
} catch (ExpiredAccessTokenException $ex1) {
|
||||||
catch(ExpiredAccessTokenException $ex1)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex1);
|
$this->log_service->warning($ex1);
|
||||||
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
return new OAuth2DirectErrorResponse($ex1->getError(), $ex1->getMessage());
|
||||||
}
|
} catch (OAuth2BaseException $ex2) {
|
||||||
catch(OAuth2BaseException $ex2)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex2);
|
$this->log_service->warning($ex2);
|
||||||
$this->checkpoint_service->trackException($ex2);
|
$this->checkpoint_service->trackException($ex2);
|
||||||
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch (Exception $ex)
|
|
||||||
{
|
|
||||||
$this->log_service->error($ex);
|
$this->log_service->error($ex);
|
||||||
$this->checkpoint_service->trackException($ex);
|
$this->checkpoint_service->trackException($ex);
|
||||||
|
|
||||||
@ -1188,8 +1217,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
|
|
||||||
static public function getTokenEndpointAuthMethodsPerClientType(IClient $client)
|
static public function getTokenEndpointAuthMethodsPerClientType(IClient $client)
|
||||||
{
|
{
|
||||||
if($client->getClientType() == IClient::ClientType_Public)
|
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||||
{
|
|
||||||
return ArrayUtils::convert2Assoc
|
return ArrayUtils::convert2Assoc
|
||||||
(
|
(
|
||||||
array
|
array
|
||||||
@ -1219,8 +1247,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
static public function getSigningAlgorithmsPerClientType(IClient $client)
|
static public function getSigningAlgorithmsPerClientType(IClient $client)
|
||||||
{
|
{
|
||||||
if($client->getClientType() == IClient::ClientType_Public)
|
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||||
{
|
|
||||||
return ArrayUtils::convert2Assoc
|
return ArrayUtils::convert2Assoc
|
||||||
(
|
(
|
||||||
array_merge
|
array_merge
|
||||||
@ -1254,8 +1281,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
static public function getKeyManagementAlgorithmsPerClientType(IClient $client)
|
static public function getKeyManagementAlgorithmsPerClientType(IClient $client)
|
||||||
{
|
{
|
||||||
if($client->getClientType() == IClient::ClientType_Public)
|
if ($client->getClientType() == IClient::ClientType_Public) {
|
||||||
{
|
|
||||||
return ArrayUtils::convert2Assoc
|
return ArrayUtils::convert2Assoc
|
||||||
(
|
(
|
||||||
array_diff
|
array_diff
|
||||||
@ -1283,8 +1309,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
$keys = $this->server_private_keys_repository->getActives();
|
$keys = $this->server_private_keys_repository->getActives();
|
||||||
$set = [];
|
$set = [];
|
||||||
|
|
||||||
foreach($keys as $private_key)
|
foreach ($keys as $private_key) {
|
||||||
{
|
|
||||||
$jwk = RSAJWKFactory::build
|
$jwk = RSAJWKFactory::build
|
||||||
(
|
(
|
||||||
new RSAJWKPEMPrivateKeySpecification
|
new RSAJWKPEMPrivateKeySpecification
|
||||||
@ -1419,8 +1444,7 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
*/
|
*/
|
||||||
public function endSession(OAuth2Request $request = null)
|
public function endSession(OAuth2Request $request = null)
|
||||||
{
|
{
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
$this->log_service->debug_msg("OAuth2Protocol::endSession");
|
$this->log_service->debug_msg("OAuth2Protocol::endSession");
|
||||||
|
|
||||||
$this->last_request = $request;
|
$this->last_request = $request;
|
||||||
@ -1497,29 +1521,22 @@ final class OAuth2Protocol implements IOAuth2Protocol
|
|||||||
if (!is_null($logged_user))
|
if (!is_null($logged_user))
|
||||||
$this->auth_service->logout();
|
$this->auth_service->logout();
|
||||||
|
|
||||||
if(!empty($redirect_logout_uri))
|
if (!empty($redirect_logout_uri)) {
|
||||||
{
|
|
||||||
return new OAuth2LogoutResponse($redirect_logout_uri, $state);
|
return new OAuth2LogoutResponse($redirect_logout_uri, $state);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
} catch (UriNotAllowedException $ex1) {
|
||||||
catch (UriNotAllowedException $ex1)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex1);
|
$this->log_service->warning($ex1);
|
||||||
$this->checkpoint_service->trackException($ex1);
|
$this->checkpoint_service->trackException($ex1);
|
||||||
|
|
||||||
return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient);
|
return new OAuth2DirectErrorResponse(OAuth2Protocol::OAuth2Protocol_Error_UnauthorizedClient);
|
||||||
}
|
} catch (OAuth2BaseException $ex2) {
|
||||||
catch(OAuth2BaseException $ex2)
|
|
||||||
{
|
|
||||||
$this->log_service->warning($ex2);
|
$this->log_service->warning($ex2);
|
||||||
$this->checkpoint_service->trackException($ex2);
|
$this->checkpoint_service->trackException($ex2);
|
||||||
|
|
||||||
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
return new OAuth2DirectErrorResponse($ex2->getError(), $ex2->getMessage());
|
||||||
}
|
} catch (Exception $ex) {
|
||||||
catch (Exception $ex)
|
|
||||||
{
|
|
||||||
$this->log_service->error($ex);
|
$this->log_service->error($ex);
|
||||||
$this->checkpoint_service->trackException($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 Auth\User;
|
||||||
use jwt\IBasicJWT;
|
use jwt\IBasicJWT;
|
||||||
|
use Models\OAuth2\Client;
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
use OAuth2\Exceptions\InvalidAuthorizationCodeException;
|
use OAuth2\Exceptions\InvalidAuthorizationCodeException;
|
||||||
use OAuth2\Exceptions\ReplayAttackException;
|
use OAuth2\Exceptions\ReplayAttackException;
|
||||||
use OAuth2\Models\AuthorizationCode;
|
use OAuth2\Models\AuthorizationCode;
|
||||||
use OAuth2\Models\AccessToken;
|
use OAuth2\Models\AccessToken;
|
||||||
use OAuth2\Models\RefreshToken;
|
use OAuth2\Models\RefreshToken;
|
||||||
use OAuth2\OAuth2Protocol;
|
|
||||||
use OAuth2\Exceptions\InvalidAccessTokenException;
|
use OAuth2\Exceptions\InvalidAccessTokenException;
|
||||||
use OAuth2\Exceptions\InvalidGrantTypeException;
|
use OAuth2\Exceptions\InvalidGrantTypeException;
|
||||||
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
||||||
use Utils\Model\Identifier;
|
use OAuth2\Requests\OAuth2PasswordlessAuthenticationRequest;
|
||||||
|
use Utils\Model\AbstractIdentifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface ITokenService
|
* Interface ITokenService
|
||||||
@ -37,13 +39,13 @@ interface ITokenService {
|
|||||||
* Creates a brand new authorization code
|
* Creates a brand new authorization code
|
||||||
* @param OAuth2AuthorizationRequest $request
|
* @param OAuth2AuthorizationRequest $request
|
||||||
* @param bool $has_previous_user_consent
|
* @param bool $has_previous_user_consent
|
||||||
* @return Identifier
|
* @return AbstractIdentifier
|
||||||
*/
|
*/
|
||||||
public function createAuthorizationCode
|
public function createAuthorizationCode
|
||||||
(
|
(
|
||||||
OAuth2AuthorizationRequest $request,
|
OAuth2AuthorizationRequest $request,
|
||||||
bool $has_previous_user_consent = false
|
bool $has_previous_user_consent = false
|
||||||
):Identifier;
|
):AbstractIdentifier;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -192,4 +194,31 @@ interface ITokenService {
|
|||||||
AccessToken $access_token = null,
|
AccessToken $access_token = null,
|
||||||
AuthorizationCode $auth_code = 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!');
|
throw new InvalidClientAuthenticationContextException('client not set!');
|
||||||
|
|
||||||
if ($client->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType())
|
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)
|
if ($client->getClientType() !== IClient::ClientType_Public)
|
||||||
throw new InvalidClientCredentials(sprintf('invalid client type %s', $client->getClientType()));
|
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));
|
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!');
|
throw new InvalidClientAuthenticationContextException('client not set!');
|
||||||
|
|
||||||
if($client->getTokenEndpointAuthInfo()->getAuthenticationMethod() !== $context->getAuthType())
|
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)
|
if($client->getClientType() !== IClient::ClientType_Confidential)
|
||||||
throw new InvalidClientCredentials(sprintf('invalid client type %s', $client->getClientType()));
|
throw new InvalidClientCredentials(sprintf('invalid client type %s', $client->getClientType()));
|
||||||
|
@ -13,12 +13,14 @@
|
|||||||
**/
|
**/
|
||||||
use OpenId\Exceptions\InvalidNonce;
|
use OpenId\Exceptions\InvalidNonce;
|
||||||
use OpenId\Helpers\OpenIdErrorMessages;
|
use OpenId\Helpers\OpenIdErrorMessages;
|
||||||
use Utils\Model\Identifier;
|
use Utils\Model\AbstractIdentifier;
|
||||||
|
use Zend\Math\Rand;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OpenIdNonce
|
* Class OpenIdNonce
|
||||||
* @package OpenId\Models
|
* @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 NonceRegexFormat = '/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/';
|
||||||
const NonceTimeFormat = '%Y-%m-%dT%H:%M:%SZ';
|
const NonceTimeFormat = '%Y-%m-%dT%H:%M:%SZ';
|
||||||
@ -139,7 +141,7 @@ final class OpenIdNonce extends Identifier
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getType()
|
public function getType():string
|
||||||
{
|
{
|
||||||
return 'nonce';
|
return 'nonce';
|
||||||
}
|
}
|
||||||
@ -151,4 +153,27 @@ final class OpenIdNonce extends Identifier
|
|||||||
{
|
{
|
||||||
return [];
|
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;
|
||||||
|
}
|
@ -11,88 +11,31 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use Utils\Services\IdentifierGenerator;
|
interface Identifier
|
||||||
/**
|
|
||||||
* Class Identifier
|
|
||||||
* @package Utils\Model
|
|
||||||
*/
|
|
||||||
abstract class 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
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getLenght()
|
public function getLength():int;
|
||||||
{
|
|
||||||
return $this->len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getLifetime()
|
|
||||||
{
|
|
||||||
return intval($this->lifetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getValue()
|
|
||||||
{
|
|
||||||
return $this->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $value
|
* @param string $value
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setValue($value)
|
public function setValue(string $value);
|
||||||
{
|
|
||||||
$this->value = $value;
|
/**
|
||||||
return $this;
|
* @return int
|
||||||
}
|
*/
|
||||||
|
public function getLifetime():int;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @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.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
use Auth\Exceptions\AuthenticationException;
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
|
use Models\OAuth2\Client;
|
||||||
|
use Models\OAuth2\OAuth2OTP;
|
||||||
use OAuth2\Models\IClient;
|
use OAuth2\Models\IClient;
|
||||||
use OpenId\Models\IOpenIdUser;
|
use OpenId\Models\IOpenIdUser;
|
||||||
/**
|
/**
|
||||||
@ -33,6 +36,8 @@ interface IAuthService
|
|||||||
const AuthenticationResponse_None = "None";
|
const AuthenticationResponse_None = "None";
|
||||||
const AuthenticationResponse_Cancel = "Cancel";
|
const AuthenticationResponse_Cancel = "Cancel";
|
||||||
|
|
||||||
|
const AuthenticationFlowPassword = "password";
|
||||||
|
const AuthenticationFlowPasswordless = "otp";
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@ -47,9 +52,19 @@ interface IAuthService
|
|||||||
* @param string $username
|
* @param string $username
|
||||||
* @param string $password
|
* @param string $password
|
||||||
* @param bool $remember_me
|
* @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
|
* @param string $username
|
||||||
|
@ -22,5 +22,5 @@ interface IdentifierGenerator {
|
|||||||
* @param Identifier $identifier
|
* @param Identifier $identifier
|
||||||
* @return Identifier
|
* @return Identifier
|
||||||
*/
|
*/
|
||||||
public function generate(Identifier $identifier);
|
public function generate(Identifier $identifier):Identifier;
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ use Zend\Crypt\Hash;
|
|||||||
* Class UniqueIdentifierGenerator
|
* Class UniqueIdentifierGenerator
|
||||||
* @package Utils\Services
|
* @package Utils\Services
|
||||||
*/
|
*/
|
||||||
abstract class UniqueIdentifierGenerator implements IdentifierGenerator
|
class UniqueIdentifierGenerator implements IdentifierGenerator
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,20 +37,13 @@ abstract class UniqueIdentifierGenerator implements IdentifierGenerator
|
|||||||
* @param Identifier $identifier
|
* @param Identifier $identifier
|
||||||
* @return Identifier
|
* @return Identifier
|
||||||
*/
|
*/
|
||||||
public function generate(Identifier $identifier){
|
public function generate(Identifier $identifier):Identifier{
|
||||||
|
|
||||||
do
|
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));
|
while(!$this->cache_service->addSingleValue($key, $key));
|
||||||
return $identifier;
|
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() {
|
$('#token_endpoint_auth_method').change(function() {
|
||||||
var auth_method = $(this).val();
|
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.
|
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
|
## create schema
|
||||||
|
|
||||||
php artisan doctrine:schema:create --sql --em=model > model.sql
|
php artisan doctrine:schema:create --sql --em=model > model.sql
|
||||||
|
@ -20,7 +20,7 @@ const xhrs = {};
|
|||||||
|
|
||||||
const cancel = (key) => {
|
const cancel = (key) => {
|
||||||
if(xhrs[key]) {
|
if(xhrs[key]) {
|
||||||
xhrs[key].abort();
|
xhrs[key].xhr.abort();
|
||||||
console.log(`aborted request ${key}`);
|
console.log(`aborted request ${key}`);
|
||||||
delete xhrs[key];
|
delete xhrs[key];
|
||||||
}
|
}
|
||||||
@ -31,11 +31,15 @@ const schedule = (key, req) => {
|
|||||||
xhrs[key] = req;
|
xhrs[key] = req;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const end = (key) => {
|
||||||
|
delete xhrs[key];
|
||||||
|
}
|
||||||
|
|
||||||
const isObjectEmpty = (obj) => {
|
const isObjectEmpty = (obj) => {
|
||||||
return Object.keys(obj).length === 0 && obj.constructor === Object ;
|
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);
|
let url = URI(endpoint);
|
||||||
|
|
||||||
if(!isObjectEmpty(params))
|
if(!isObjectEmpty(params))
|
||||||
@ -45,26 +49,47 @@ export const getRawRequest = (endpoint, errorHandler = null) => (params) => {
|
|||||||
|
|
||||||
cancel(key);
|
cancel(key);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
let req = http.get(url.toString());
|
||||||
let req = http.get(url.toString())
|
schedule(key, req);
|
||||||
.timeout({
|
|
||||||
|
return req.timeout({
|
||||||
response: 60000,
|
response: 60000,
|
||||||
deadline: 60000,
|
deadline: 60000,
|
||||||
})
|
}).then((res) => {
|
||||||
.end(
|
|
||||||
(err, res) => {
|
|
||||||
if (err || !res.ok) {
|
|
||||||
if(errorHandler) {
|
|
||||||
errorHandler(err, res);
|
|
||||||
}
|
|
||||||
return reject({ err, res })
|
|
||||||
}
|
|
||||||
let json = res.body;
|
let json = res.body;
|
||||||
return resolve({response: json});
|
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);
|
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);
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {getRawRequest} from '../base_actions'
|
import {getRawRequest, postRawRequest} from '../base_actions'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const verifyAccount = (email) => {
|
export const verifyAccount = (email) => {
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
email: email
|
email: email
|
||||||
};
|
};
|
||||||
@ -10,3 +10,13 @@ export const verifyAccount = (email) => {
|
|||||||
return getRawRequest(window.VERIFY_ACCOUNT_ENDPOINT)(params);
|
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 Chip from '@material-ui/core/Chip';
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import Checkbox from '@material-ui/core/Checkbox';
|
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 {MuiThemeProvider, createMuiTheme} from '@material-ui/core/styles';
|
||||||
import DividerWithText from '../components/divider_with_text';
|
import DividerWithText from '../components/divider_with_text';
|
||||||
import Visibility from '@material-ui/icons/Visibility';
|
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 IconButton from '@material-ui/core/IconButton';
|
||||||
import {emailValidator} from '../validator';
|
import {emailValidator} from '../validator';
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
|
||||||
const EmailInputForm = ({onValidateEmail, onHandleUserNameChange, disableInput, emailError}) => {
|
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={userNameValue} id="username" name="username"/>
|
||||||
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
|
<input type="hidden" value={csrfToken} id="_token" name="_token"/>
|
||||||
|
<input type="hidden" value="password" id="flow" name="flow"/>
|
||||||
{shouldShowCaptcha() &&
|
{shouldShowCaptcha() &&
|
||||||
<ReCAPTCHA
|
<ReCAPTCHA
|
||||||
className={styles.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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<hr className={styles.separator}/>
|
<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">
|
<Link href={forgotPasswordAction} target="_self" variant="body2">
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</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}) => {
|
const EmailErrorActions = ({createAccountAction, onValidateEmail, disableInput}) => {
|
||||||
return(
|
return(
|
||||||
<Grid container style={{alignItems: 'center', marginTop: "20%"}}>
|
<Grid container style={{alignItems: 'center', marginTop: "20%"}}>
|
||||||
@ -201,6 +294,7 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
user_name: props.userName,
|
user_name: props.userName,
|
||||||
user_password: '',
|
user_password: '',
|
||||||
@ -214,7 +308,9 @@ class LoginPage extends React.Component {
|
|||||||
captcha_value: '',
|
captcha_value: '',
|
||||||
showPassword: false,
|
showPassword: false,
|
||||||
disableInput: false,
|
disableInput: false,
|
||||||
|
authFlow: props.flow,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onHandleUserNameChange = this.onHandleUserNameChange.bind(this);
|
this.onHandleUserNameChange = this.onHandleUserNameChange.bind(this);
|
||||||
this.onValidateEmail = this.onValidateEmail.bind(this);
|
this.onValidateEmail = this.onValidateEmail.bind(this);
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
this.handleDelete = this.handleDelete.bind(this);
|
||||||
@ -224,6 +320,29 @@ class LoginPage extends React.Component {
|
|||||||
this.shouldShowCaptcha = this.shouldShowCaptcha.bind(this);
|
this.shouldShowCaptcha = this.shouldShowCaptcha.bind(this);
|
||||||
this.handleClickShowPassword = this.handleClickShowPassword.bind(this);
|
this.handleClickShowPassword = this.handleClickShowPassword.bind(this);
|
||||||
this.handleMouseDownPassword = this.handleMouseDownPassword.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() {
|
shouldShowCaptcha() {
|
||||||
@ -236,7 +355,11 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
onAuthenticate(ev) {
|
onAuthenticate(ev) {
|
||||||
if (this.state.user_password == '') {
|
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();
|
ev.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -276,9 +399,11 @@ class LoginPage extends React.Component {
|
|||||||
if (!emailValidator(this.state.user_name)) {
|
if (!emailValidator(this.state.user_name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({...this.state, disableInput: true});
|
this.setState({...this.state, disableInput: true});
|
||||||
verifyAccount(this.state.user_name).then((payload) => {
|
verifyAccount(this.state.user_name).then((payload) => {
|
||||||
let {response} = payload;
|
let {response} = payload;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
user_pic: response.pic,
|
user_pic: response.pic,
|
||||||
@ -291,10 +416,18 @@ class LoginPage extends React.Component {
|
|||||||
disableInput: false
|
disableInput: false
|
||||||
})
|
})
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
let {body} = error.res;
|
|
||||||
let newErrors = {}
|
let {response, status, message} = error;
|
||||||
|
|
||||||
|
let newErrors = {};
|
||||||
|
|
||||||
newErrors['password'] = '';
|
newErrors['password'] = '';
|
||||||
newErrors['email'] = "We could not find an Account with that email Address";
|
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.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
user_pic: null,
|
user_pic: null,
|
||||||
@ -308,7 +441,10 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDelete() {
|
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) {
|
handleClickShowPassword(ev) {
|
||||||
@ -365,12 +501,13 @@ class LoginPage extends React.Component {
|
|||||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||||
verifyEmailAction={this.props.verifyEmailAction}
|
verifyEmailAction={this.props.verifyEmailAction}
|
||||||
helpAction={this.props.helpAction}
|
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 )
|
// proceed to ask for password ( 2nd step )
|
||||||
<>
|
<>
|
||||||
<PasswordInputForm
|
<PasswordInputForm
|
||||||
@ -394,9 +531,32 @@ class LoginPage extends React.Component {
|
|||||||
forgotPasswordAction={this.props.forgotPasswordAction}
|
forgotPasswordAction={this.props.forgotPasswordAction}
|
||||||
verifyEmailAction={this.props.verifyEmailAction}
|
verifyEmailAction={this.props.verifyEmailAction}
|
||||||
helpAction={this.props.helpAction}
|
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>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ $base-color:#3fa2f7;
|
|||||||
color: $base-color;
|
color: $base-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.inner_container {
|
.inner_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 64px;
|
margin-top: 64px;
|
||||||
@ -72,3 +73,8 @@ $base-color:#3fa2f7;
|
|||||||
.valid_user_name_chip{
|
.valid_user_name_chip{
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.otp_p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
@ -32,8 +32,10 @@
|
|||||||
appLogo: '{{ Config::get("app.logo_url") }}',
|
appLogo: '{{ Config::get("app.logo_url") }}',
|
||||||
formAction: '{{ URL::action("UserController@postLogin") }}',
|
formAction: '{{ URL::action("UserController@postLogin") }}',
|
||||||
accountVerifyAction : '{{URL::action("UserController@getAccount")}}',
|
accountVerifyAction : '{{URL::action("UserController@getAccount")}}',
|
||||||
|
emitOtpAction : '{{URL::action("UserController@emitOTP")}}',
|
||||||
authError: authError,
|
authError: authError,
|
||||||
captchaPublicKey: '{{ Config::get("recaptcha.public_key") }}',
|
captchaPublicKey: '{{ Config::get("recaptcha.public_key") }}',
|
||||||
|
flow: 'password',
|
||||||
thirdPartyProviders: [
|
thirdPartyProviders: [
|
||||||
@foreach($supported_providers as $provider => $label)
|
@foreach($supported_providers as $provider => $label)
|
||||||
{label: "{{$label}}", name:"{{$provider}}"},
|
{label: "{{$label}}", name:"{{$provider}}"},
|
||||||
@ -63,9 +65,12 @@
|
|||||||
@if(Session::has('user_verified'))
|
@if(Session::has('user_verified'))
|
||||||
config.user_verified = {{Session::get('user_verified')}};
|
config.user_verified = {{Session::get('user_verified')}};
|
||||||
@endif
|
@endif
|
||||||
|
@if(Session::has('flow'))
|
||||||
|
config.flow = '{{Session::get('flow')}}';
|
||||||
|
@endif
|
||||||
|
|
||||||
window.VERIFY_ACCOUNT_ENDPOINT = config.accountVerifyAction;
|
window.VERIFY_ACCOUNT_ENDPOINT = config.accountVerifyAction;
|
||||||
|
window.EMIT_OTP_ENDPOINT = config.emitOtpAction;
|
||||||
</script>
|
</script>
|
||||||
{!! HTML::script('assets/login.js') !!}
|
{!! HTML::script('assets/login.js') !!}
|
||||||
@append
|
@append
|
180
resources/views/emails/oauth2_passwordless_otp.blade.php
Normal file
180
resources/views/emails/oauth2_passwordless_otp.blade.php
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<!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">
|
||||||
|
|
||||||
|
<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>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="#"></a>
|
<a class="navbar-brand" href="#" target="_self"></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul id='main-menu' class="nav navbar-nav">
|
<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">
|
<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>
|
{{ __('OAUTH2 Console') }}<b class="caret"></b>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a href='{!!URL::action("AdminController@listOAuth2Clients")!!}'>{{ __('OAUTH2 Applications') }}</a></li>
|
<li><a target="_self" 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@editIssuedGrants")!!}'>{{ __('Issued OAUTH2 Grants') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@if(Auth::user()->isOpenIdServerAdmin() || Auth::user()->isOAuth2ServerAdmin() || Auth::user()->isSuperAdmin())
|
@if(Auth::user()->isOpenIdServerAdmin() || Auth::user()->isOAuth2ServerAdmin() || Auth::user()->isSuperAdmin())
|
||||||
<li id='server-admin' class="dropdown">
|
<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') }}
|
{{ __('Server Administration') }}
|
||||||
<b class="caret"></b>
|
<b class="caret"></b>
|
||||||
</a>
|
</a>
|
||||||
@ -33,29 +33,29 @@
|
|||||||
@if(Auth::user()->isSuperAdmin() || Auth::user()->isOpenIdServerAdmin())
|
@if(Auth::user()->isSuperAdmin() || Auth::user()->isOpenIdServerAdmin())
|
||||||
<li class="dropdown-header">{{ __('Security') }}</li>
|
<li class="dropdown-header">{{ __('Security') }}</li>
|
||||||
@if(Auth::user()->isSuperAdmin())
|
@if(Auth::user()->isSuperAdmin())
|
||||||
<li><a href='{!!URL::action("AdminController@listUsers")!!}'>{{ __('Users') }}</a></li>
|
<li><a target="_self" 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@listGroups")!!}'>{{ __('Groups') }}</a></li>
|
||||||
@endif
|
@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>
|
<li role="separator" class="divider"></li>
|
||||||
@endif
|
@endif
|
||||||
@if(Auth::user()->isOAuth2ServerAdmin())
|
@if(Auth::user()->isOAuth2ServerAdmin())
|
||||||
<li class="dropdown-header">{{ __('OAUTH2') }}</li>
|
<li class="dropdown-header">{{ __('OAUTH2') }}</li>
|
||||||
<li><a href='{!!URL::action("AdminController@listServerPrivateKeys")!!}'>{{ __('Private Keys') }}</a></li>
|
<li><a target="_self" href='{!!URL::action("AdminController@listServerPrivateKeys")!!}'>{{ __('Private Keys') }}</a></li>
|
||||||
<li><a href='{!!URL::action("AdminController@listResourceServers")!!}'>{{ __('Resource Servers') }}</a></li>
|
<li><a target="_self" href='{!!URL::action("AdminController@listResourceServers")!!}'>{{ __('Resource Servers') }}</a></li>
|
||||||
<li><a href='{!!URL::action("AdminController@listApiScopeGroups")!!}'>{{ __('Api Scope Groups') }}</a></li>
|
<li><a target="_self" 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@listLockedClients")!!}'>{{ __('Locked Clients') }}</a></li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
@endif
|
@endif
|
||||||
@if(Auth::user()->isOpenIdServerAdmin())
|
@if(Auth::user()->isOpenIdServerAdmin())
|
||||||
<li class="dropdown-header">{{ __('Server') }}</li>
|
<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
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
<li><a title="help" target="_blank" href="mailto:{!! Config::get("app.help_email") !!}">Help</a></li>
|
<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>
|
</ul>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,6 +15,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@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">
|
<div class="form-group">
|
||||||
<label for="default_max_age">Default Max. Age (optional) <span class="glyphicon glyphicon-info-sign accordion-toggle"
|
<label for="default_max_age">Default Max. Age (optional) <span class="glyphicon glyphicon-info-sign accordion-toggle"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
@ -44,7 +44,8 @@ Route::group(array('middleware' => ['ssl']), function () {
|
|||||||
|
|
||||||
Route::group(array('prefix' => 'login'), function () {
|
Route::group(array('prefix' => 'login'), function () {
|
||||||
Route::get('', "UserController@getLogin");
|
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::post('', ['middleware' => 'csrf', 'uses' => 'UserController@postLogin']);
|
||||||
Route::get('cancel', "UserController@cancelLogin");
|
Route::get('cancel', "UserController@cancelLogin");
|
||||||
Route::group(array('prefix' => '{provider}'), function () {
|
Route::group(array('prefix' => '{provider}'), function () {
|
||||||
@ -56,7 +57,7 @@ Route::group(array('middleware' => ['ssl']), function () {
|
|||||||
// registration routes
|
// registration routes
|
||||||
Route::group(array('prefix' => 'register'), function () {
|
Route::group(array('prefix' => 'register'), function () {
|
||||||
Route::get('', 'Auth\RegisterController@showRegistrationForm');
|
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 () {
|
Route::group(array('prefix' => 'verification'), function () {
|
||||||
@ -100,13 +101,14 @@ Route::group(['namespace' => 'OAuth2', 'middleware' => ['ssl']], function () {
|
|||||||
Route::get('/.well-known/openid-configuration', "OAuth2ProviderController@discovery");
|
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('/check-session', "OAuth2ProviderController@checkSessionIFrame");
|
||||||
Route::get('/end-session', "OAuth2ProviderController@endSession");
|
Route::get('/end-session', "OAuth2ProviderController@endSession");
|
||||||
Route::post('/end-session', "OAuth2ProviderController@endSession");
|
Route::post('/end-session', "OAuth2ProviderController@endSession");
|
||||||
|
|
||||||
//authorization endpoint
|
//authorization endpoint
|
||||||
Route::any('/auth', "OAuth2ProviderController@auth");
|
Route::any('/auth', "OAuth2ProviderController@auth")->mi;
|
||||||
// OIDC
|
// OIDC
|
||||||
// certificates
|
// certificates
|
||||||
Route::get('/certs', "OAuth2ProviderController@certs");
|
Route::get('/certs', "OAuth2ProviderController@certs");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -14,8 +14,8 @@
|
|||||||
use Models\OAuth2\ApiEndpoint;
|
use Models\OAuth2\ApiEndpoint;
|
||||||
use Models\OAuth2\Api;
|
use Models\OAuth2\Api;
|
||||||
use Models\OAuth2\ApiScope;
|
use Models\OAuth2\ApiScope;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
/**
|
/**
|
||||||
* Class ApiEndpointTest
|
* Class ApiEndpointTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -13,8 +13,8 @@
|
|||||||
**/
|
**/
|
||||||
use Models\OAuth2\ApiScope;
|
use Models\OAuth2\ApiScope;
|
||||||
use Models\OAuth2\Api;
|
use Models\OAuth2\Api;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
/**
|
/**
|
||||||
* Class ApiScopeTest
|
* Class ApiScopeTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -13,8 +13,8 @@
|
|||||||
**/
|
**/
|
||||||
use Models\OAuth2\Api;
|
use Models\OAuth2\Api;
|
||||||
use Models\OAuth2\ResourceServer;
|
use Models\OAuth2\ResourceServer;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
/**
|
/**
|
||||||
* Class ApiTest
|
* Class ApiTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,7 +16,7 @@ use OpenId\Helpers\AssociationFactory;
|
|||||||
use OpenId\OpenIdProtocol;
|
use OpenId\OpenIdProtocol;
|
||||||
use Utils\Services\UtilsServiceCatalog;
|
use Utils\Services\UtilsServiceCatalog;
|
||||||
use Utils\Exceptions\UnacquiredLockException;
|
use Utils\Exceptions\UnacquiredLockException;
|
||||||
use Tests\BrowserKitTestCase;
|
use Mockery;
|
||||||
/**
|
/**
|
||||||
* Class AssociationServiceTest
|
* Class AssociationServiceTest
|
||||||
*/
|
*/
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Queue;
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -20,7 +20,7 @@ use LaravelDoctrine\ORM\Facades\EntityManager;
|
|||||||
/**
|
/**
|
||||||
* Class ClientApiTest
|
* Class ClientApiTest
|
||||||
*/
|
*/
|
||||||
class ClientApiTest extends \Tests\BrowserKitTestCase {
|
class ClientApiTest extends BrowserKitTestCase {
|
||||||
|
|
||||||
private $current_realm;
|
private $current_realm;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,8 +16,9 @@ use jwk\JSONWebKeyPublicKeyUseValues;
|
|||||||
use Models\OAuth2\Client;
|
use Models\OAuth2\Client;
|
||||||
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use TestKeys;
|
||||||
/**
|
/**
|
||||||
* Class ClientPublicKeyApiTest
|
* Class ClientPublicKeyApiTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,7 +16,6 @@ use Utils\Services\UtilsServiceCatalog;
|
|||||||
use OpenId\Services\OpenIdServiceCatalog;
|
use OpenId\Services\OpenIdServiceCatalog;
|
||||||
use Auth\Repositories\IUserRepository;
|
use Auth\Repositories\IUserRepository;
|
||||||
use Auth\IAuthenticationExtensionService;
|
use Auth\IAuthenticationExtensionService;
|
||||||
use Tests\TestCase;
|
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
/**
|
/**
|
||||||
* Class CustomAuthProviderTest
|
* Class CustomAuthProviderTest
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -15,7 +15,6 @@ use OpenId\Helpers\AssocHandleGenerator;
|
|||||||
use OpenId\Helpers\OpenIdCryptoHelper;
|
use OpenId\Helpers\OpenIdCryptoHelper;
|
||||||
use OpenId\Requests\OpenIdDHAssociationSessionRequest;
|
use OpenId\Requests\OpenIdDHAssociationSessionRequest;
|
||||||
use Zend\Crypt\PublicKey\DiffieHellman;
|
use Zend\Crypt\PublicKey\DiffieHellman;
|
||||||
use Tests\TestCase;
|
|
||||||
/**
|
/**
|
||||||
* Class DiffieHellmanTest
|
* Class DiffieHellmanTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -11,7 +11,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
/***
|
/***
|
||||||
* Class DiscoveryControllerTest
|
* Class DiscoveryControllerTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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\Session;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OAuth2ProtectedApiTest
|
* Class OAuth2ProtectedApiTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,13 +12,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use OAuth2\OAuth2Protocol;
|
use OAuth2\OAuth2Protocol;
|
||||||
use Utils\Services\IAuthService;
|
use Utils\Services\IAuthService;
|
||||||
use Utils\Services\UtilsServiceCatalog;
|
use Utils\Services\UtilsServiceCatalog;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
/**
|
/**
|
||||||
* Class OAuth2ProtocolTest
|
* Class OAuth2ProtocolTest
|
||||||
* Test Suite for OAuth2 Protocol
|
* Test Suite for OAuth2 Protocol
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use OAuth2ProtectedApiTest;
|
|
||||||
use App\libs\OAuth2\IUserScopes;
|
use App\libs\OAuth2\IUserScopes;
|
||||||
/**
|
/**
|
||||||
* Class OAuth2UserRegistrationServiceApiTest
|
* Class OAuth2UserRegistrationServiceApiTest
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2020 OpenStack Foundation
|
* Copyright 2020 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -13,7 +13,7 @@
|
|||||||
**/
|
**/
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
use LaravelDoctrine\ORM\Facades\Registry;
|
use LaravelDoctrine\ORM\Facades\Registry;
|
||||||
use Doctrine\Common\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Models\SSO\DisqusSSOProfile;
|
use App\Models\SSO\DisqusSSOProfile;
|
||||||
use App\Models\Utils\BaseEntity;
|
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
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -11,6 +11,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
use jwa\JSONWebSignatureAndEncryptionAlgorithms;
|
||||||
@ -35,6 +36,9 @@ use jwt\impl\UnsecuredJWT;
|
|||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
use Database\Seeders\TestSeeder;
|
use Database\Seeders\TestSeeder;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OIDCProtocolTest
|
* Class OIDCProtocolTest
|
||||||
* http://openid.net/wordpress-content/uploads/2015/02/OpenID-Connect-Conformance-Profiles.pdf
|
* 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()
|
protected function prepareForTests()
|
||||||
{
|
{
|
||||||
parent::prepareForTests();
|
parent::prepareForTests();
|
||||||
App::singleton(UtilsServiceCatalog::ServerConfigurationService, 'StubServerConfigurationService');
|
App::singleton(UtilsServiceCatalog::ServerConfigurationService, StubServerConfigurationService::class);
|
||||||
$this->current_realm = Config::get('app.url');
|
$this->current_realm = Config::get('app.url');
|
||||||
Session::start();
|
Session::start();
|
||||||
}
|
}
|
||||||
@ -120,12 +124,12 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
|
|
||||||
// do login
|
// do login
|
||||||
$response = $this->action('POST', "UserController@postLogin",
|
$response = $this->action('POST', "UserController@postLogin",
|
||||||
array
|
[
|
||||||
(
|
|
||||||
'username' => ' sebastian@tipit.net ',
|
'username' => ' sebastian@tipit.net ',
|
||||||
'password' => ' 1qaz2wsx ',
|
'password' => ' 1qaz2wsx ',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token(),
|
||||||
)
|
'flow' => 'password',
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertResponseStatus(302);
|
$this->assertResponseStatus(302);
|
||||||
@ -169,6 +173,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -254,6 +259,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -334,6 +340,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -447,6 +454,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -530,6 +538,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -554,7 +563,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
$client_id = '%2E%2D%5F%7E87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
$client_id = '%2E%2D%5F%7E87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
||||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
||||||
$use_enc = true
|
$use_enc = true
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
$params = array(
|
$params = array(
|
||||||
@ -592,6 +602,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -701,7 +712,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client',
|
||||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg',
|
||||||
$use_enc = true
|
$use_enc = true
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
$params = array(
|
$params = array(
|
||||||
@ -752,6 +764,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -857,7 +870,8 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
return $access_token;
|
return $access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetRefreshTokenWithPromptSetToConsentLogin(){
|
public function testGetRefreshTokenWithPromptSetToConsentLogin()
|
||||||
|
{
|
||||||
|
|
||||||
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
|
$client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
|
||||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
||||||
@ -898,6 +912,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_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_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client';
|
||||||
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
$client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg';
|
||||||
@ -1039,6 +1055,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => $json_response['required_params_valid_values']["_token"]
|
'_token' => $json_response['required_params_valid_values']["_token"]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1157,6 +1174,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1354,6 +1372,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1500,6 +1519,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1638,6 +1658,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1816,6 +1837,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1967,6 +1989,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2047,6 +2070,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2131,6 +2155,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2240,6 +2265,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2376,6 +2402,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2545,6 +2572,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2651,6 +2679,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2871,6 +2900,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -2988,6 +3018,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_token' => Session::token()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -3095,6 +3126,7 @@ final class OIDCProtocolTest extends OpenStackIDBaseTest
|
|||||||
(
|
(
|
||||||
'username' => 'sebastian@tipit.net',
|
'username' => 'sebastian@tipit.net',
|
||||||
'password' => '1qaz2wsx',
|
'password' => '1qaz2wsx',
|
||||||
|
'flow' => 'password',
|
||||||
'_token' => Session::token()
|
'_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
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -23,6 +23,7 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Models\OpenId\OpenIdTrustedSite;
|
use Models\OpenId\OpenIdTrustedSite;
|
||||||
use OpenId\Extensions\Implementations\OpenIdSREGExtension_1_0;
|
use OpenId\Extensions\Implementations\OpenIdSREGExtension_1_0;
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
/**
|
/**
|
||||||
* Class OpenIdProtocolTest
|
* Class OpenIdProtocolTest
|
||||||
* Test Suite for OpenId Protocol
|
* Test Suite for OpenId Protocol
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 Openstack Foundation
|
* Copyright 2015 Openstack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -13,7 +13,6 @@
|
|||||||
**/
|
**/
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
/**
|
/**
|
||||||
* Class OpenStackIDBaseTest
|
* Class OpenStackIDBaseTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -15,7 +15,6 @@ use Models\OAuth2\ResourceServer;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
/**
|
/**
|
||||||
* Class ResourceServerApiTest
|
* Class ResourceServerApiTest
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2016 OpenStack Foundation
|
* Copyright 2016 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -14,10 +14,9 @@
|
|||||||
use OpenId\Services\OpenIdServiceCatalog;
|
use OpenId\Services\OpenIdServiceCatalog;
|
||||||
use Utils\Services\IAuthService;
|
use Utils\Services\IAuthService;
|
||||||
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
use OpenId\Repositories\IOpenIdTrustedSiteRepository;
|
||||||
use OpenId\Models\IOpenIdUser;
|
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
|
use Mockery;
|
||||||
/**
|
/**
|
||||||
* Class TrustedSitesServiceTest
|
* Class TrustedSitesServiceTest
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2020 OpenStack Foundation
|
* Copyright 2020 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -11,7 +11,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use Tests\TestCase;
|
|
||||||
use App\Http\Utils\CookieSameSitePolicy;
|
use App\Http\Utils\CookieSameSitePolicy;
|
||||||
/**
|
/**
|
||||||
* Class UserAgentTests
|
* Class UserAgentTests
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,7 +12,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
use Auth\UserNameGeneratorService;
|
use Auth\UserNameGeneratorService;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
use LaravelDoctrine\ORM\Facades\EntityManager;
|
use LaravelDoctrine\ORM\Facades\EntityManager;
|
||||||
use Auth\User;
|
use Auth\User;
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php namespace Tests;
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 OpenStack Foundation
|
* Copyright 2015 OpenStack Foundation
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -13,7 +13,6 @@
|
|||||||
**/
|
**/
|
||||||
use OpenId\Xrds\XRDSDocumentBuilder;
|
use OpenId\Xrds\XRDSDocumentBuilder;
|
||||||
use OpenId\Xrds\XRDSService;
|
use OpenId\Xrds\XRDSService;
|
||||||
use Tests\BrowserKitTestCase;
|
|
||||||
/**
|
/**
|
||||||
* Class XRDSDocumentTest
|
* Class XRDSDocumentTest
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user