openstackid/app/libs/openid/handlers/OpenIdAuthenticationRequest...

504 lines
21 KiB
PHP

<?php
namespace openid\handlers;
use Exception;
use openid\exceptions\InvalidAssociationTypeException;
use openid\exceptions\InvalidOpenIdAuthenticationRequestMode;
use openid\exceptions\InvalidOpenIdMessageException;
use openid\exceptions\OpenIdInvalidRealmException;
use openid\exceptions\ReplayAttackException;
use openid\helpers\AssociationFactory;
use openid\helpers\OpenIdErrorMessages;
use openid\helpers\OpenIdSignatureBuilder;
use openid\model\IAssociation;
use openid\OpenIdMessage;
use openid\OpenIdProtocol;
use openid\requests\contexts\RequestContext;
use openid\requests\OpenIdAuthenticationRequest;
use openid\responses\contexts\ResponseContext;
use openid\responses\OpenIdImmediateNegativeAssertion;
use openid\responses\OpenIdIndirectGenericErrorResponse;
use openid\responses\OpenIdNonImmediateNegativeAssertion;
use openid\responses\OpenIdPositiveAssertionResponse;
use openid\services\IAssociationService;
use openid\services\IMementoOpenIdSerializerService;
use openid\services\INonceService;
use openid\services\IServerConfigurationService;
use openid\services\IServerExtensionsService;
use openid\services\ITrustedSitesService;
use utils\services\IAuthService;
use utils\services\ICheckPointService;
use utils\services\ILogService;
/**
* Class OpenIdAuthenticationRequestHandler
* Implements
* http://openid.net/specs/openid-authentication-2_0.html#requesting_authentication
* http://openid.net/specs/openid-authentication-2_0.html#responding_to_authentication
* @package openid\handlers
*/
final class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler
{
/**
* @var IAuthService
*/
private $auth_service;
/**
* @var IMementoOpenIdSerializerService
*/
private $memento_service;
/**
* @var IOpenIdAuthenticationStrategy
*/
private $auth_strategy;
/**
* @var IServerExtensionsService
*/
private $server_extensions_service;
/**
* @var IAssociationService
*/
private $association_service;
/**
* @var ITrustedSitesService
*/
private $trusted_sites_service;
/**
* @var IServerConfigurationService
*/
private $server_configuration_service;
/**
* @var
*/
private $extensions;
/**
* @var
*/
private $current_request_context;
/**
* @var INonceService
*/
private $nonce_service;
/**
* @param IAuthService $authService
* @param IMementoOpenIdSerializerService $memento_service
* @param IOpenIdAuthenticationStrategy $auth_strategy
* @param IServerExtensionsService $server_extensions_service
* @param IAssociationService $association_service
* @param ITrustedSitesService $trusted_sites_service
* @param IServerConfigurationService $server_configuration_service
* @param INonceService $nonce_service
* @param ILogService $log
* @param ICheckPointService $checkpoint_service
* @param $successor
*/
public function __construct(
IAuthService $authService,
IMementoOpenIdSerializerService $memento_service,
IOpenIdAuthenticationStrategy $auth_strategy,
IServerExtensionsService $server_extensions_service,
IAssociationService $association_service,
ITrustedSitesService $trusted_sites_service,
IServerConfigurationService $server_configuration_service,
INonceService $nonce_service,
ILogService $log,
ICheckPointService $checkpoint_service,
$successor
) {
parent::__construct($successor, $log, $checkpoint_service);
$this->auth_service = $authService;
$this->memento_service = $memento_service;
$this->auth_strategy = $auth_strategy;
$this->server_extensions_service = $server_extensions_service;
$this->association_service = $association_service;
$this->trusted_sites_service = $trusted_sites_service;
$this->server_configuration_service = $server_configuration_service;
$this->extensions = $this->server_extensions_service->getAllActiveExtensions();
$this->nonce_service = $nonce_service;
}
/**
* @param OpenIdMessage $message
* @return OpenIdImmediateNegativeAssertion|OpenIdIndirectGenericErrorResponse|OpenIdNonImmediateNegativeAssertion|OpenIdPositiveAssertionResponse
* @throws \openid\exceptions\InvalidOpenIdAuthenticationRequestMode
*/
protected function internalHandle(OpenIdMessage $message)
{
$this->current_request = null;
try {
$this->current_request = new OpenIdAuthenticationRequest(
$message,
$this->server_configuration_service->getUserIdentityEndpointURL('@identifier')
);
if (!$this->current_request->isValid()) {
throw new InvalidOpenIdMessageException(OpenIdErrorMessages::InvalidOpenIdAuthenticationRequestMessage);
}
$this->current_request_context = new RequestContext;
$mode = $this->current_request->getMode();
switch ($mode) {
case OpenIdProtocol::SetupMode: {
return $this->doSetupMode();
}
break;
case OpenIdProtocol::ImmediateMode: {
return $this->doImmediateMode();
}
break;
default:
throw new InvalidOpenIdAuthenticationRequestMode(sprintf(OpenIdErrorMessages::InvalidAuthenticationRequestModeMessage,
$mode));
break;
}
} catch (InvalidAssociationTypeException $inv_assoc_type) {
$this->checkpoint_service->trackException($inv_assoc_type);
$this->log_service->warning($inv_assoc_type);
if (!is_null($this->current_request)) {
$this->log_service->warning_msg("current request: ".$this->current_request);
}
return new OpenIdIndirectGenericErrorResponse($inv_assoc_type->getMessage(), null, null,$this->current_request);
} catch (OpenIdInvalidRealmException $inv_realm_ex) {
$this->checkpoint_service->trackException($inv_realm_ex);
$this->log_service->warning($inv_realm_ex);
if (!is_null($this->current_request)) {
$this->log_service->warning_msg("current request: ".$this->current_request);
}
return new OpenIdIndirectGenericErrorResponse($inv_realm_ex->getMessage(), null, null, $this->current_request);
} catch (ReplayAttackException $replay_ex) {
$this->checkpoint_service->trackException($replay_ex);
$this->log_service->warning($replay_ex);
if (!is_null($this->current_request)) {
$this->log_service->warning_msg("current request: ".$this->current_request);;
}
return new OpenIdIndirectGenericErrorResponse($replay_ex->getMessage(), null, null, $this->current_request);
} catch (InvalidOpenIdMessageException $inv_msg_ex) {
$this->checkpoint_service->trackException($inv_msg_ex);
$this->log_service->warning($inv_msg_ex);
if (!is_null($this->current_request)) {
$this->log_service->warning_msg("current request: ".$this->current_request);;
}
return new OpenIdIndirectGenericErrorResponse($inv_msg_ex->getMessage(), null, null, $this->current_request);
} catch (Exception $ex) {
$this->checkpoint_service->trackException($ex);
$this->log_service->error($ex);
if (!is_null($this->current_request)) {
$this->log_service->warning_msg("current request: ".$this->current_request);;
}
return new OpenIdIndirectGenericErrorResponse("Server Error", null, null, $this->current_request);
}
}
/**
* @return OpenIdIndirectGenericErrorResponse|OpenIdNonImmediateNegativeAssertion|OpenIdPositiveAssertionResponse
* @throws \Exception
*/
private function doSetupMode()
{
$authentication_response = $this->auth_service->getUserAuthenticationResponse();
if ($authentication_response == IAuthService::AuthenticationResponse_Cancel) {
//clear saved data ...
$this->memento_service->forget();
$this->auth_service->clearUserAuthenticationResponse();
$this->auth_service->clearUserAuthorizationResponse();
return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo());
}
if (!$this->auth_service->isUserLogged()) {
return $this->doLogin();
}
//user already logged
$currentUser = $this->auth_service->getCurrentUser();
if (!$this->current_request->isIdentitySelectByOP()) {
$current_claimed_id = $this->current_request->getClaimedId();
$current_identity = $this->current_request->getIdentity();
// check is claimed identity match with current one
// if not logs out and do re login
$current_user = $this->auth_service->getCurrentUser();
if (is_null($current_user)) {
throw new Exception("User not set!");
}
$current_owned_identity = $this->server_configuration_service->getUserIdentityEndpointURL($current_user->getIdentifier());
if ($current_claimed_id != $current_owned_identity && $current_identity != $current_owned_identity) {
$this->log_service->warning_msg(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage,
$current_owned_identity, $current_identity));
$this->auth_service->logout();
return $this->doLogin();
}
}
$authorization_response = $this->auth_service->getUserAuthorizationResponse();
if ($authorization_response !== IAuthService::AuthorizationResponse_None) {
return $this->checkAuthorizationResponse($authorization_response);
}
// $authorization_response is none ...
$this->current_request_context->cleanTrustedData();
foreach ($this->extensions as $ext) {
$data = $ext->getTrustedData($this->current_request);
$this->current_request_context->setTrustedData($data);
}
$requested_data = $this->current_request_context->getTrustedData();
$sites = $this->trusted_sites_service->getTrustedSites(
$currentUser,
$this->current_request->getRealm(),
$requested_data
);
//check trusted sites
if (is_null($sites) || count($sites) == 0) {
return $this->doConsentProcess();
}
//there are trusted sites ... check the former authorization decision
$site = $sites[0];
$policy = $site->getAuthorizationPolicy();
switch ($policy) {
case IAuthService::AuthorizationResponse_AllowForever: {
//save former user choice on session
$this->auth_service->setUserAuthorizationResponse($policy);
return $this->doAssertion();
}
break;
case IAuthService::AuthorizationResponse_DenyForever:
// black listed site
return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage,
$site->getRealm()), null, null, $this->current_request);
break;
default:
throw new Exception("Invalid Realm Policy");
break;
}
}
/**
* @return mixed
*/
private function doLogin()
{
//do login process
foreach ($this->extensions as $ext) {
$ext->parseRequest($this->current_request, $this->current_request_context);
}
$this->memento_service->serialize($this->current_request->getMessage()->createMemento());
return $this->auth_strategy->doLogin($this->current_request, $this->current_request_context);
}
/**
* Create Positive Identity Assertion
* implements http://openid.net/specs/openid-authentication-2_0.html#positive_assertions
* @return OpenIdPositiveAssertionResponse
* @throws InvalidAssociationTypeException
*/
private function doAssertion()
{
$currentUser = $this->auth_service->getCurrentUser();
$context = new ResponseContext;
//initial signature params
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_OpEndpoint));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Realm));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ReturnTo));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Nonce));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_AssocHandle));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_ClaimedId));
$context->addSignParam(OpenIdProtocol::param(OpenIdProtocol::OpenIDProtocol_Identity));
$op_endpoint = $this->server_configuration_service->getOPEndpointURL();
$identity = $this->server_configuration_service->getUserIdentityEndpointURL($currentUser->getIdentifier());
$nonce = $this->nonce_service->generateNonce();
$realm = $this->current_request->getRealm();
$response = new OpenIdPositiveAssertionResponse($op_endpoint, $identity, $identity,
$this->current_request->getReturnTo(), $nonce->getRawFormat(), $realm);
foreach ($this->extensions as $ext) {
$ext->prepareResponse($this->current_request, $response, $context);
}
//check former assoc handle...
if (is_null($assoc_handle = $this->current_request->getAssocHandle()) || is_null($association = $this->association_service->getAssociation($assoc_handle))) {
//create private association ...
$association = $this->association_service->addAssociation(AssociationFactory::getInstance()->buildPrivateAssociation($realm,
$this->server_configuration_service->getConfigValue("Private.Association.Lifetime")));
$response->setAssocHandle($association->getHandle());
if (!empty($assoc_handle)) {
$response->setInvalidateHandle($assoc_handle);
}
} else {
if ($association->getType() != IAssociation::TypeSession) {
throw new InvalidAssociationTypeException(OpenIdErrorMessages::InvalidAssociationTypeMessage);
}
$response->setAssocHandle($assoc_handle);
}
//create signature ...
OpenIdSignatureBuilder::build($context, $association->getMacFunction(), $association->getSecret(), $response);
/*
* To prevent replay attacks, the OP MUST NOT issue more than one verification response for each
* authentication response it had previously issued. An authentication response and its matching
* verification request may be identified by their "openid.response_nonce" values.
* so associate $nonce with signature and realm
*/
$this->nonce_service->associateNonce($nonce, $response->getSig(), $realm);
//do cleaning ...
$this->memento_service->forget();
$this->auth_service->clearUserAuthorizationResponse();
return $response;
}
/**
* @return mixed
*/
private function doConsentProcess()
{
//do consent process
$this->memento_service->serialize($this->current_request->getMessage()->createMemento());
foreach ($this->extensions as $ext) {
$ext->parseRequest($this->current_request, $this->current_request_context);
}
return $this->auth_strategy->doConsent($this->current_request, $this->current_request_context);
}
/**
* @param $authorization_response
* @return OpenIdNonImmediateNegativeAssertion|OpenIdPositiveAssertionResponse
* @throws \Exception
*/
private function checkAuthorizationResponse($authorization_response)
{
// check response
$currentUser = $this->auth_service->getCurrentUser();
switch ($authorization_response) {
case IAuthService::AuthorizationResponse_AllowForever: {
$this->current_request_context->cleanTrustedData();
foreach ($this->extensions as $ext) {
$data = $ext->getTrustedData($this->current_request);
$this->current_request_context->setTrustedData($data);
}
$this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(),
IAuthService::AuthorizationResponse_AllowForever, $this->current_request_context->getTrustedData());
return $this->doAssertion();
}
break;
case IAuthService::AuthorizationResponse_AllowOnce:
return $this->doAssertion();
break;
case IAuthService::AuthorizationResponse_DenyOnce: {
$this->memento_service->forget();
$this->auth_service->clearUserAuthorizationResponse();
return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo());
}
break;
case IAuthService::AuthorizationResponse_DenyForever: {
$this->current_request_context->cleanTrustedData();
foreach ($this->extensions as $ext) {
$data = $ext->getTrustedData($this->current_request);
$this->current_request_context->setTrustedData($data);
}
$this->trusted_sites_service->addTrustedSite($currentUser, $this->current_request->getRealm(),
IAuthService::AuthorizationResponse_DenyForever, $this->current_request_context->getTrustedData());
$this->memento_service->forget();
$this->auth_service->clearUserAuthorizationResponse();
return new OpenIdNonImmediateNegativeAssertion($this->current_request->getReturnTo());
}
break;
default:
$this->memento_service->forget();
$this->auth_service->clearUserAuthorizationResponse();
throw new \Exception("Invalid Authorization response!");
break;
}
}
/**
* @return OpenIdImmediateNegativeAssertion|OpenIdIndirectGenericErrorResponse|OpenIdPositiveAssertionResponse
*/
protected function doImmediateMode()
{
if (!$this->auth_service->isUserLogged()) {
return new OpenIdImmediateNegativeAssertion($this->current_request->getReturnTo());
}
$currentUser = $this->auth_service->getCurrentUser();
$this->current_request_context->cleanTrustedData();
foreach ($this->extensions as $ext) {
$data = $ext->getTrustedData($this->current_request);
$this->current_request_context->setTrustedData($data);
}
$requested_data = $this->current_request_context->getTrustedData();
$sites = $this->trusted_sites_service->getTrustedSites($currentUser, $this->current_request->getRealm(),
$requested_data);
if (is_null($sites) || count($sites) == 0) {
//need setup to continue
return new OpenIdImmediateNegativeAssertion($this->current_request->getReturnTo());
}
$site = $sites[0];
$policy = $site->getAuthorizationPolicy();
switch ($policy) {
case IAuthService::AuthorizationResponse_DenyForever: {
// black listed site by user
return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage,
$site->getRealm()), null, null, $this->current_request);
}
break;
case IAuthService::AuthorizationResponse_AllowForever: {
//save former user choice on session
$this->auth_service->setUserAuthorizationResponse($policy);
return $this->doAssertion();
}
break;
default:
return new OpenIdIndirectGenericErrorResponse(sprintf(OpenIdErrorMessages::RealmNotAllowedByUserMessage,
$this->current_request->getRealm()), null, null, $this->current_request);
break;
}
}
/**
* @param OpenIdMessage $message
* @return bool
*/
protected function canHandle(OpenIdMessage $message)
{
$res = OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($message);
return $res;
}
}