399 lines
13 KiB
PHP
399 lines
13 KiB
PHP
<?php namespace OAuth2\GrantTypes;
|
|
|
|
/**
|
|
* 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 Exception;
|
|
use OAuth2\Exceptions\ExpiredAuthorizationCodeException;
|
|
use OAuth2\Exceptions\InvalidApplicationType;
|
|
use OAuth2\Exceptions\InvalidAuthorizationCodeException;
|
|
use OAuth2\Exceptions\InvalidClientException;
|
|
use OAuth2\Exceptions\InvalidClientType;
|
|
use OAuth2\Exceptions\InvalidOAuth2Request;
|
|
use OAuth2\Exceptions\InvalidRedeemAuthCodeException;
|
|
use OAuth2\Exceptions\OAuth2GenericException;
|
|
use OAuth2\Exceptions\UnAuthorizedClientException;
|
|
use OAuth2\Exceptions\UriNotAllowedException;
|
|
use OAuth2\Factories\OAuth2AccessTokenResponseFactory;
|
|
use OAuth2\Models\IClient;
|
|
use OAuth2\Repositories\IClientRepository;
|
|
use OAuth2\Services\ITokenService;
|
|
use OAuth2\OAuth2Protocol;
|
|
use OAuth2\Repositories\IServerPrivateKeyRepository;
|
|
use OAuth2\Requests\OAuth2AccessTokenRequestAuthCode;
|
|
use OAuth2\Requests\OAuth2AuthenticationRequest;
|
|
use OAuth2\Requests\OAuth2AuthorizationRequest;
|
|
use OAuth2\Requests\OAuth2Request;
|
|
use OAuth2\Requests\OAuth2TokenRequest;
|
|
use OAuth2\Responses\OAuth2AccessTokenResponse;
|
|
use OAuth2\Responses\OAuth2AuthorizationResponse;
|
|
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\IUserConsentService;
|
|
use OAuth2\Strategies\IOAuth2AuthenticationStrategy;
|
|
use Utils\Services\IAuthService;
|
|
use Utils\Services\ILogService;
|
|
|
|
/**
|
|
* Class AuthorizationCodeGrantType
|
|
* Authorization Code Grant Implementation
|
|
* The authorization code grant type is used to obtain both access
|
|
* tokens and refresh tokens and is optimized for confidential clients.
|
|
* Since this is a redirection-based flow, the client must be capable of
|
|
* interacting with the resource owner's user-agent (typically a web
|
|
* browser) and capable of receiving incoming requests (via redirection)
|
|
* from the authorization server.
|
|
* @see http://tools.ietf.org/html/rfc6749#section-4.1
|
|
* @package OAuth2\GrantTypes
|
|
*/
|
|
class AuthorizationCodeGrantType extends InteractiveGrantType
|
|
{
|
|
|
|
/**
|
|
* AuthorizationCodeGrantType 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)
|
|
{
|
|
if
|
|
(
|
|
$request instanceof OAuth2AuthorizationRequest &&
|
|
$request->isValid() &&
|
|
OAuth2Protocol::responseTypeBelongsToFlow
|
|
(
|
|
$request->getResponseType(false),
|
|
OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode
|
|
)
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
if
|
|
(
|
|
$request instanceof OAuth2TokenRequest &&
|
|
$request->isValid() &&
|
|
$request->getGrantType() == $this->getType()
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return mixed|string
|
|
*/
|
|
public function getType()
|
|
{
|
|
return OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getResponseType()
|
|
{
|
|
return OAuth2Protocol::getValidResponseTypes(OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode);
|
|
}
|
|
|
|
/**
|
|
* 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 InvalidAuthorizationCodeException
|
|
* @throws ExpiredAuthorizationCodeException
|
|
* @throws Exception
|
|
* @throws InvalidClientException
|
|
* @throws UnAuthorizedClientException
|
|
* @throws UriNotAllowedException
|
|
*/
|
|
public function completeFlow(OAuth2Request $request)
|
|
{
|
|
|
|
if (!($request instanceof OAuth2AccessTokenRequestAuthCode))
|
|
{
|
|
throw new InvalidOAuth2Request;
|
|
}
|
|
|
|
try
|
|
{
|
|
parent::completeFlow($request);
|
|
|
|
$this->checkClientTypeAccess($this->client_auth_context->getClient());
|
|
|
|
$current_redirect_uri = $request->getRedirectUri();
|
|
//verify redirect uri
|
|
if (!$this->current_client->isUriAllowed($current_redirect_uri))
|
|
{
|
|
throw new UriNotAllowedException
|
|
(
|
|
$current_redirect_uri
|
|
);
|
|
}
|
|
|
|
$code = $request->getCode();
|
|
// verify that the authorization code is valid
|
|
// The client MUST NOT use the authorization code
|
|
// more than once. If an authorization code is used more than
|
|
// once, the authorization server MUST deny the request and SHOULD
|
|
// revoke (when possible) all tokens previously issued based on
|
|
// that authorization code. The authorization code is bound to
|
|
// the client identifier and redirection URI.
|
|
$auth_code = $this->token_service->getAuthorizationCode($code);
|
|
|
|
// reload session state
|
|
$client_id = $auth_code->getClientId();
|
|
|
|
$this->security_context_service->save
|
|
(
|
|
$this->security_context_service->get()
|
|
->setAuthTimeRequired
|
|
(
|
|
$auth_code->isAuthTimeRequested()
|
|
)
|
|
->setRequestedUserId
|
|
(
|
|
$auth_code->getUserId()
|
|
)
|
|
);
|
|
|
|
//ensure that the authorization code was issued to the authenticated
|
|
//confidential client, or if the client is public, ensure that the
|
|
//code was issued to "client_id" in the request
|
|
if ($client_id != $this->client_auth_context->getId())
|
|
{
|
|
throw new InvalidRedeemAuthCodeException
|
|
(
|
|
sprintf
|
|
(
|
|
"auth code was issued for another client id!."
|
|
)
|
|
);
|
|
}
|
|
|
|
// ensure that the "redirect_uri" parameter is present if the
|
|
// "redirect_uri" parameter was included in the initial authorization
|
|
// and if included ensure that their values are identical.
|
|
$redirect_uri = $auth_code->getRedirectUri();
|
|
|
|
if (!empty($redirect_uri) && $redirect_uri !== $current_redirect_uri)
|
|
{
|
|
throw new UriNotAllowedException($current_redirect_uri);
|
|
}
|
|
|
|
$this->principal_service->register
|
|
(
|
|
$auth_code->getUserId(),
|
|
$auth_code->getAuthTime()
|
|
);
|
|
|
|
$response = OAuth2AccessTokenResponseFactory::build
|
|
(
|
|
$this->token_service,
|
|
$auth_code,
|
|
$request
|
|
);
|
|
|
|
$this->security_context_service->clear();
|
|
|
|
return $response;
|
|
}
|
|
catch (InvalidAuthorizationCodeException $ex)
|
|
{
|
|
$this->log_service->error($ex);
|
|
$this->security_context_service->clear();
|
|
throw new InvalidRedeemAuthCodeException
|
|
(
|
|
$ex->getMessage()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param OAuth2Request $request
|
|
* @return mixed|null|OAuth2AccessTokenRequestAuthCode
|
|
*/
|
|
public function buildTokenRequest(OAuth2Request $request)
|
|
{
|
|
if ($request instanceof OAuth2TokenRequest)
|
|
{
|
|
if ($request->getGrantType() !== $this->getType())
|
|
{
|
|
return null;
|
|
}
|
|
return new OAuth2AccessTokenRequestAuthCode($request->getMessage());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param IClient $client
|
|
* @throws InvalidApplicationType
|
|
* @throws InvalidClientType
|
|
* @return void
|
|
*/
|
|
protected function checkClientTypeAccess(IClient $client)
|
|
{
|
|
if
|
|
(
|
|
!(
|
|
$client->getClientType() === IClient::ClientType_Confidential ||
|
|
$client->getApplicationType() === IClient::ApplicationType_Native
|
|
)
|
|
)
|
|
{
|
|
throw new InvalidApplicationType
|
|
(
|
|
sprintf
|
|
(
|
|
"client id %s - Application type must be %s or %s",
|
|
$client->getClientId(),
|
|
IClient::ClientType_Confidential,
|
|
IClient::ApplicationType_Native
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param OAuth2AuthorizationRequest $request
|
|
* @param bool $has_former_consent
|
|
* @return OAuth2AuthorizationResponse
|
|
* @throws OAuth2GenericException
|
|
*/
|
|
protected function buildResponse(OAuth2AuthorizationRequest $request, $has_former_consent)
|
|
{
|
|
$user = $this->auth_service->getCurrentUser();
|
|
|
|
// build current audience ...
|
|
$audience = $this->scope_service->getStrAudienceByScopeNames
|
|
(
|
|
explode
|
|
(
|
|
OAuth2Protocol::OAuth2Protocol_Scope_Delimiter,
|
|
$request->getScope()
|
|
)
|
|
);
|
|
|
|
$nonce = null;
|
|
$prompt = null;
|
|
|
|
if($request instanceof OAuth2AuthenticationRequest)
|
|
{
|
|
$nonce = $request->getNonce();
|
|
$prompt = $request->getPrompt(true);
|
|
}
|
|
|
|
$auth_code = $this->token_service->createAuthorizationCode
|
|
(
|
|
$user->getId(),
|
|
$request->getClientId(),
|
|
$request->getScope(),
|
|
$audience,
|
|
$request->getRedirectUri(),
|
|
$request->getAccessType(),
|
|
$request->getApprovalPrompt(),
|
|
$has_former_consent,
|
|
$request->getState(),
|
|
$nonce,
|
|
$request->getResponseType(),
|
|
$prompt
|
|
);
|
|
|
|
if (is_null($auth_code))
|
|
{
|
|
throw new OAuth2GenericException("Invalid Auth Code");
|
|
}
|
|
// http://openid.net/specs/openid-connect-session-1_0.html#CreatingUpdatingSessions
|
|
$session_state = $this->getSessionState
|
|
(
|
|
self::getOrigin
|
|
(
|
|
$request->getRedirectUri()
|
|
),
|
|
$request->getClientId(),
|
|
|
|
$this->principal_service->get()->getOPBrowserState()
|
|
);
|
|
|
|
return new OAuth2AuthorizationResponse
|
|
(
|
|
$request->getRedirectUri(),
|
|
$auth_code->getValue(),
|
|
$request->getScope(),
|
|
$request->getState(),
|
|
$session_state
|
|
);
|
|
}
|
|
|
|
|
|
} |