Updated OAuth2 error handling.
This commit is contained in:
parent
53822c5fd9
commit
c259493303
1 changed files with 75 additions and 125 deletions
|
@ -23,12 +23,28 @@ final class OAuth2Routes extends RouteHandler {
|
|||
throw new InvalidArgumentException('$getAuthInfo must be callable');
|
||||
}
|
||||
|
||||
private static function error(string $code, string $message = '', string $url = ''): array {
|
||||
private static function error(
|
||||
$response,
|
||||
string $code,
|
||||
string $message = '',
|
||||
string $uri = '',
|
||||
bool $authzHeader = false
|
||||
) {
|
||||
$info = ['error' => $code];
|
||||
if($message !== '')
|
||||
$info['error_description'] = $message;
|
||||
if($url !== '')
|
||||
$info['error_uri'] = $url;
|
||||
if($uri !== '')
|
||||
$info['error_uri'] = $uri;
|
||||
|
||||
if($authzHeader) {
|
||||
$wwwAuth = sprintf('Basic realm="%s"', $_SERVER['HTTP_HOST']);
|
||||
foreach($info as $name => $value)
|
||||
$wwwAuth .= sprintf(', %s="%s"', $name, rawurlencode($value));
|
||||
|
||||
$response->setStatusCode($authzHeader ? 401 : 400);
|
||||
$response->setHeader('WWW-Authenticate', $wwwAuth);
|
||||
} else
|
||||
$response->setStatusCode(400);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
@ -327,10 +343,8 @@ final class OAuth2Routes extends RouteHandler {
|
|||
public function postRequestAuthorise($response, $request) {
|
||||
$response->setHeader('Cache-Control', 'no-store');
|
||||
|
||||
if(!$request->isFormContent()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_request', 'Your request must use content type application/x-www-form-urlencoded.');
|
||||
}
|
||||
if(!$request->isFormContent())
|
||||
return self::error($response, 'invalid_request', 'Your request must use content type application/x-www-form-urlencoded.');
|
||||
|
||||
$content = $request->getContent();
|
||||
|
||||
|
@ -340,9 +354,7 @@ final class OAuth2Routes extends RouteHandler {
|
|||
$clientId = $authzHeader[0];
|
||||
$clientSecret = $authzHeader[1] ?? '';
|
||||
} elseif($authzHeader[0] !== '') {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
return self::error('invalid_client', 'You must use the Basic method for Authorization parameters.');
|
||||
return self::error($response, 'invalid_client', 'You must use the Basic method for Authorization parameters.', authzHeader: true);
|
||||
} else {
|
||||
$clientId = (string)$content->getParam('client_id');
|
||||
$clientSecret = '';
|
||||
|
@ -352,31 +364,23 @@ final class OAuth2Routes extends RouteHandler {
|
|||
try {
|
||||
$appInfo = $appsData->getAppInfo(clientId: $clientId, deleted: false);
|
||||
} catch(RuntimeException $ex) {
|
||||
if($authzHeader[0] === '') {
|
||||
$response->setStatusCode(400);
|
||||
} else {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
}
|
||||
|
||||
return self::error('invalid_client', 'No application has been registered with this client ID.');
|
||||
return self::error(
|
||||
$response, 'invalid_client', 'No application has been registered with this client ID.',
|
||||
authzHeader: $authzHeader[0] !== ''
|
||||
);
|
||||
}
|
||||
|
||||
$appAuthenticated = false;
|
||||
if($clientSecret !== '') {
|
||||
// TODO: rate limiting
|
||||
if(!$appInfo->verifyClientSecret($clientSecret)) {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
return self::error('invalid_client', 'Provided client secret is not correct for this application.');
|
||||
}
|
||||
if(!$appInfo->verifyClientSecret($clientSecret))
|
||||
return self::error($response, 'invalid_client', 'Provided client secret is not correct for this application.', authzHeader: true);
|
||||
}
|
||||
|
||||
$scopes = self::filterScopes((string)$content->getParam('scope'));
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_scope', 'An invalid scope was requested.');
|
||||
}
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
|
||||
return self::error($response, 'invalid_scope', 'An invalid scope was requested.');
|
||||
|
||||
$scope = implode(' ', $scopes);
|
||||
|
||||
$deviceInfo = $this->oauth2Ctx->getDevicesData()->createDevice($appInfo, $scope);
|
||||
|
@ -428,10 +432,8 @@ final class OAuth2Routes extends RouteHandler {
|
|||
if($request->getMethod() === 'OPTIONS')
|
||||
return 204;
|
||||
|
||||
if(!$request->isFormContent()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_request', 'Your request must use content type application/x-www-form-urlencoded.');
|
||||
}
|
||||
if(!$request->isFormContent())
|
||||
return self::error($response, 'invalid_request', 'Your request must use content type application/x-www-form-urlencoded.');
|
||||
|
||||
$content = $request->getContent();
|
||||
|
||||
|
@ -442,9 +444,7 @@ final class OAuth2Routes extends RouteHandler {
|
|||
$clientId = $authzHeader[0];
|
||||
$clientSecret = $authzHeader[1] ?? '';
|
||||
} elseif($authzHeader[0] !== '') {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
return self::error('invalid_client', 'You must either use the Basic method for Authorization or use the client_id and client_secret parameters.');
|
||||
return self::error($response, 'invalid_client', 'You must either use the Basic method for Authorization or use the client_id and client_secret parameters.', authzHeader: true);
|
||||
} else {
|
||||
$clientId = (string)$content->getParam('client_id');
|
||||
$clientSecret = (string)$content->getParam('client_secret');
|
||||
|
@ -454,30 +454,15 @@ final class OAuth2Routes extends RouteHandler {
|
|||
try {
|
||||
$appInfo = $appsData->getAppInfo(clientId: $clientId, deleted: false);
|
||||
} catch(RuntimeException $ex) {
|
||||
if($authzHeader[0] === '') {
|
||||
$response->setStatusCode(400);
|
||||
} else {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
}
|
||||
|
||||
return self::error('invalid_client', 'No application has been registered with this client id.');
|
||||
return self::error($response, 'invalid_client', 'No application has been registered with this client id.', authzHeader: $authzHeader[0] !== '');
|
||||
}
|
||||
|
||||
$appAuthenticated = false;
|
||||
if($clientSecret !== '') {
|
||||
// TODO: rate limiting
|
||||
$appAuthenticated = $appInfo->verifyClientSecret($clientSecret);
|
||||
if(!$appAuthenticated) {
|
||||
if($authzHeader[0] === '') {
|
||||
$response->setStatusCode(400);
|
||||
} else {
|
||||
$response->setStatusCode(401);
|
||||
$response->setHeader('WWW-Authenticate', 'Basic');
|
||||
}
|
||||
|
||||
return self::error('invalid_client', 'Provided client secret is not correct for this application.');
|
||||
}
|
||||
if(!$appAuthenticated)
|
||||
return self::error($response, 'invalid_client', 'Provided client secret is not correct for this application.', authzHeader: $authzHeader[0] !== '');
|
||||
}
|
||||
|
||||
$type = (string)$content->getParam('grant_type');
|
||||
|
@ -489,27 +474,20 @@ final class OAuth2Routes extends RouteHandler {
|
|||
code: (string)$content->getParam('code'),
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'No authorisation request with this code exists.');
|
||||
return self::error($response, 'invalid_grant', 'No authorisation request with this code exists.');
|
||||
}
|
||||
|
||||
if($authsInfo->hasExpired()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'Authorisation request has expired.');
|
||||
}
|
||||
|
||||
if(!$authsInfo->verifyCodeChallenge((string)$content->getParam('code_verifier'))) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_request', 'Code challenge verification failed.');
|
||||
}
|
||||
if($authsInfo->hasExpired())
|
||||
return self::error($response, 'invalid_grant', 'Authorisation request has expired.');
|
||||
if(!$authsInfo->verifyCodeChallenge((string)$content->getParam('code_verifier')))
|
||||
return self::error($response, 'invalid_request', 'Code challenge verification failed.');
|
||||
|
||||
$authsData->deleteAuthorisation($authsInfo);
|
||||
|
||||
$scopes = $authsInfo->getScopes();
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
}
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
|
||||
return self::error($response, 'invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
|
||||
$scope = implode(' ', $scopes);
|
||||
|
||||
$tokensData = $this->oauth2Ctx->getTokensData();
|
||||
|
@ -530,19 +508,13 @@ final class OAuth2Routes extends RouteHandler {
|
|||
try {
|
||||
$refreshInfo = $tokensData->getRefreshInfo((string)$content->getParam('refresh_token'), OAuth2TokensData::REFRESH_BY_TOKEN);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'No such refresh token exists.');
|
||||
return self::error($response, 'invalid_grant', 'No such refresh token exists.');
|
||||
}
|
||||
|
||||
if($refreshInfo->getAppId() !== $appInfo->getId()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'This refresh token is not associated with this application.');
|
||||
}
|
||||
|
||||
if($refreshInfo->hasExpired()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'This refresh token has expired.');
|
||||
}
|
||||
if($refreshInfo->getAppId() !== $appInfo->getId())
|
||||
return self::error($response, 'invalid_grant', 'This refresh token is not associated with this application.');
|
||||
if($refreshInfo->hasExpired())
|
||||
return self::error($response, 'invalid_grant', 'This refresh token has expired.');
|
||||
|
||||
$tokensData->deleteRefresh($refreshInfo);
|
||||
|
||||
|
@ -553,10 +525,8 @@ final class OAuth2Routes extends RouteHandler {
|
|||
$newScopes = self::filterScopes((string)$content->getParam('scope'));
|
||||
$oldScopes = [];
|
||||
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $newScopes)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
}
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $newScopes))
|
||||
return self::error($response, 'invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
|
||||
$accessInfo = $tokensData->createAccess(
|
||||
$appInfo,
|
||||
|
@ -569,21 +539,15 @@ final class OAuth2Routes extends RouteHandler {
|
|||
if($appInfo->shouldIssueRefreshToken())
|
||||
$refreshInfo = $this->oauth2Ctx->createRefresh($appInfo, $accessInfo);
|
||||
} elseif($type === 'client_credentials') {
|
||||
if(!$appInfo->isConfidential()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('unauthorized_client', 'This application is not allowed to use this grant type.');
|
||||
}
|
||||
|
||||
if(!$appAuthenticated) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_client', 'Application must authenticate with client secret in order to use this grant type.');
|
||||
}
|
||||
if(!$appInfo->isConfidential())
|
||||
return self::error($response, 'unauthorized_client', 'This application is not allowed to use this grant type.');
|
||||
if(!$appAuthenticated)
|
||||
return self::error($response, 'invalid_client', 'Application must authenticate with client secret in order to use this grant type.');
|
||||
|
||||
$scopes = self::filterScopes((string)$content->getParam('scope'));
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
}
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
|
||||
return self::error($response, 'invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
|
||||
$scope = implode(' ', $scopes);
|
||||
|
||||
$accessInfo = $this->oauth2Ctx->getTokensData()->createAccess($appInfo, scope: $scope);
|
||||
|
@ -598,44 +562,34 @@ final class OAuth2Routes extends RouteHandler {
|
|||
code: (string)$content->getParam('device_code')
|
||||
);
|
||||
} catch(RuntimeException) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'No such device code exists.');
|
||||
return self::error($response, 'invalid_grant', 'No such device code exists.');
|
||||
}
|
||||
|
||||
if($deviceInfo->hasExpired()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('expired_token', 'This device code has expired.');
|
||||
}
|
||||
if($deviceInfo->hasExpired())
|
||||
return self::error($response, 'expired_token', 'This device code has expired.');
|
||||
|
||||
if($deviceInfo->isSpeedy()) {
|
||||
$devicesData->incrementDevicePollInterval($deviceInfo);
|
||||
$response->setStatusCode(400);
|
||||
return self::error('slow_down', 'You are polling too fast, please increase your interval by 5 seconds.');
|
||||
return self::error($response, 'slow_down', 'You are polling too fast, please increase your interval by 5 seconds.');
|
||||
}
|
||||
|
||||
if($deviceInfo->isPending()) {
|
||||
$devicesData->bumpDevicePollTime($deviceInfo);
|
||||
$response->setStatusCode(400);
|
||||
return self::error('authorization_pending', 'User has not yet completed authorisation, check again in a bit.');
|
||||
return self::error($response, 'authorization_pending', 'User has not yet completed authorisation, check again in a bit.');
|
||||
}
|
||||
|
||||
$devicesData->deleteDevice($deviceInfo);
|
||||
|
||||
if(!$deviceInfo->isApproved()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('access_denied', 'User has rejected authorisation attempt.');
|
||||
}
|
||||
if(!$deviceInfo->isApproved())
|
||||
return self::error($response, 'access_denied', 'User has rejected authorisation attempt.');
|
||||
|
||||
if(!$deviceInfo->hasUserId()) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_request', 'Device code was approved but has no associated user, please contact the system administrator because something is wrong.');
|
||||
}
|
||||
if(!$deviceInfo->hasUserId())
|
||||
return self::error($response, 'invalid_request', 'Device code was approved but has no associated user, please contact the system administrator because something is wrong.');
|
||||
|
||||
$scopes = $deviceInfo->getScopes();
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
}
|
||||
if(!$this->oauth2Ctx->validateScopes($appInfo, $scopes))
|
||||
return self::error($response, 'invalid_scope', 'One or more requested scopes are no longer valid for this application, please restart authorisation.');
|
||||
|
||||
$scope = implode(' ', $scopes);
|
||||
|
||||
$tokensData = $this->oauth2Ctx->getTokensData();
|
||||
|
@ -651,15 +605,11 @@ final class OAuth2Routes extends RouteHandler {
|
|||
|
||||
if($appInfo->shouldIssueRefreshToken())
|
||||
$refreshInfo = $this->oauth2Ctx->createRefresh($appInfo, $accessInfo);
|
||||
} else {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('unsupported_grant_type', 'Requested grant type is not supported by this server.');
|
||||
}
|
||||
} else
|
||||
return self::error($response, 'unsupported_grant_type', 'Requested grant type is not supported by this server.');
|
||||
|
||||
if(empty($accessInfo)) {
|
||||
$response->setStatusCode(400);
|
||||
return self::error('invalid_grant', 'Failed to request access token.');
|
||||
}
|
||||
if(empty($accessInfo))
|
||||
return self::error($response, 'invalid_grant', 'Failed to request access token.');
|
||||
|
||||
$result = [
|
||||
'access_token' => $accessInfo->getToken(),
|
||||
|
|
Loading…
Reference in a new issue