openstackid/app/libs/OAuth2/GrantTypes/PasswordlessGrantType.php

379 lines
12 KiB
PHP

<?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;
}
}