Skip to content
Snippets Groups Projects
Commit 0517db8c authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Implemented client credentials grant type

parent b94e3eeb
No related branches found
No related tags found
No related merge requests found
...@@ -17,6 +17,35 @@ class ClientAuthChecker { ...@@ -17,6 +17,35 @@ class ClientAuthChecker {
public function validateClientAuth(): void { public function validateClientAuth(): void {
$basic = $this->getBasicAuthArray();
$clientId = $basic[0];
$clientSecret = $basic[1];
$client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($clientId);
if ($client === null) {
throw new UnauthorizedException("Client '$clientId' not configured");
}
if ($clientSecret !== $client->secret) {
throw new UnauthorizedException("Invalid client secret");
}
}
public function validateCliClientAuth(): CliClient {
$basic = $this->getBasicAuthArray();
$clientId = $basic[0];
$clientSecret = $basic[1];
$client = $this->locator->getOAuth2ClientDAO()->getCliClient($clientId, $clientSecret);
if ($client === null) {
throw new UnauthorizedException("Client '$clientId' not configured or wrong password");
}
return $client;
}
private function getBasicAuthArray(): array {
$headers = apache_request_headers(); $headers = apache_request_headers();
if (!isset($headers['Authorization'])) { if (!isset($headers['Authorization'])) {
...@@ -29,16 +58,7 @@ class ClientAuthChecker { ...@@ -29,16 +58,7 @@ class ClientAuthChecker {
if (count($basic) !== 2) { if (count($basic) !== 2) {
throw new BadRequestException("Malformed Basic-Auth header"); throw new BadRequestException("Malformed Basic-Auth header");
} }
$clientId = $basic[0]; return $basic;
$clientSecret = $basic[1];
$client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($clientId);
if ($client === null) {
throw new UnauthorizedException("Client '$clientId' not configured");
}
if ($clientSecret !== $client->secret) {
throw new UnauthorizedException("Invalid client secret");
}
} else { } else {
throw new UnauthorizedException("Expected Basic authorization header"); throw new UnauthorizedException("Expected Basic authorization header");
} }
......
...@@ -107,11 +107,7 @@ class JWKSHandler { ...@@ -107,11 +107,7 @@ class JWKSHandler {
$dao->updatePublicJWK($jwk); $dao->updatePublicJWK($jwk);
} }
} else { } else {
$errorMessage = 'Error while retrieving JWKS: ' . curl_error($conn); error_log('Error while retrieving JWKS from ' . $url);
error_log($result);
curl_close($conn);
http_response_code(500);
die($errorMessage);
} }
curl_close($conn); curl_close($conn);
......
...@@ -91,7 +91,7 @@ class OAuth2RequestHandler { ...@@ -91,7 +91,7 @@ class OAuth2RequestHandler {
return $redirectUrl; return $redirectUrl;
} }
public function handleAccessTokenRequest($params): array { public function handleGetTokenFromCodeRequest($params): array {
$this->locator->getClientAuthChecker()->validateClientAuth(); $this->locator->getClientAuthChecker()->validateClientAuth();
...@@ -123,6 +123,19 @@ class OAuth2RequestHandler { ...@@ -123,6 +123,19 @@ class OAuth2RequestHandler {
return $response; return $response;
} }
public function handleClientCredentialsRequest($params): array {
$client = $this->locator->getClientAuthChecker()->validateCliClientAuth();
$accessTokenData = new AccessTokenData();
$accessTokenData->clientId = $client->id;
$accessTokenData->userId = $client->id;
$accessTokenData->scope = $client->scope;
$accessTokenData->audience = $client->audience;
return $this->getAccessTokenResponse($accessTokenData, false);
}
public function handleRefreshTokenRequest($params): array { public function handleRefreshTokenRequest($params): array {
$this->locator->getClientAuthChecker()->validateClientAuth(); $this->locator->getClientAuthChecker()->validateClientAuth();
...@@ -182,14 +195,16 @@ class OAuth2RequestHandler { ...@@ -182,14 +195,16 @@ class OAuth2RequestHandler {
return $scope; return $scope;
} }
private function getAccessTokenResponse(AccessTokenData $tokenData) { private function getAccessTokenResponse(AccessTokenData $tokenData, bool $refreshToken = true) {
$result = []; $result = [];
$result['access_token'] = $this->locator->getTokenBuilder()->getAccessToken($tokenData); $result['access_token'] = $this->locator->getTokenBuilder()->getAccessToken($tokenData);
$result['token_type'] = 'Bearer'; $result['token_type'] = 'Bearer';
$result['expires_in'] = $tokenData->expirationTime - time(); $result['expires_in'] = $tokenData->expirationTime - time();
if ($refreshToken) {
$result['refresh_token'] = $this->buildRefreshToken($tokenData); $result['refresh_token'] = $this->buildRefreshToken($tokenData);
}
if ($tokenData->scope !== null && in_array('openid', $tokenData->scope)) { if ($tokenData->scope !== null && in_array('openid', $tokenData->scope)) {
$result['id_token'] = $this->locator->getTokenBuilder()->getIdToken($tokenData); $result['id_token'] = $this->locator->getTokenBuilder()->getIdToken($tokenData);
......
...@@ -58,10 +58,16 @@ class TokenBuilder { ...@@ -58,10 +58,16 @@ class TokenBuilder {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
$user = $this->locator->getUserDAO()->findUserById($tokenData->userId); $user = $this->locator->getUserDAO()->findUserById($tokenData->userId);
if ($user === null) {
// CLI client
$sub = $tokenData->clientId;
} else {
$sub = $user->id;
}
$payload = array( $payload = array(
'iss' => $this->locator->config->jwtIssuer, 'iss' => $this->locator->config->jwtIssuer,
'sub' => strval($user->id), 'sub' => strval($sub),
'iat' => intval($tokenData->creationTime), 'iat' => intval($tokenData->creationTime),
'exp' => intval($tokenData->expirationTime), 'exp' => intval($tokenData->expirationTime),
'aud' => $this->getAudience($tokenData), 'aud' => $this->getAudience($tokenData),
...@@ -77,7 +83,15 @@ class TokenBuilder { ...@@ -77,7 +83,15 @@ class TokenBuilder {
private function getAudience(AccessTokenData $tokenData) { private function getAudience(AccessTokenData $tokenData) {
if ($tokenData->audience !== null) {
return $this->getAudienceClaim($tokenData->audience);
}
$client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($tokenData->clientId); $client = $this->locator->getOAuth2ClientDAO()->getOAuth2ClientByClientId($tokenData->clientId);
if ($client === null) {
// CLI client without audience
return null;
}
$audiences = [$tokenData->clientId]; $audiences = [$tokenData->clientId];
...@@ -90,6 +104,10 @@ class TokenBuilder { ...@@ -90,6 +104,10 @@ class TokenBuilder {
} }
} }
return $this->getAudienceClaim($audiences);
}
private function getAudienceClaim($audiences) {
if (count($audiences) === 1) { if (count($audiences) === 1) {
// according to RFC 7519 audience can be a single value or an array // according to RFC 7519 audience can be a single value or an array
return $audiences[0]; return $audiences[0];
......
...@@ -20,4 +20,6 @@ interface OAuth2ClientDAO { ...@@ -20,4 +20,6 @@ interface OAuth2ClientDAO {
* the secret, not the database id). * the secret, not the database id).
*/ */
function getOAuth2ClientByClientId($clientId): ?OAuth2Client; function getOAuth2ClientByClientId($clientId): ?OAuth2Client;
function getCliClient(string $clientId, string $secret): ?CliClient;
} }
...@@ -246,4 +246,41 @@ class MySQLOAuth2ClientDAO extends BaseMySQLDAO implements OAuth2ClientDAO { ...@@ -246,4 +246,41 @@ class MySQLOAuth2ClientDAO extends BaseMySQLDAO implements OAuth2ClientDAO {
return $client; return $client;
} }
function getCliClient(string $clientId, string $secret): ?CliClient {
$dbh = $this->getDBHandler();
// Load clients info
$queryClient = "SELECT scope, audience FROM cli_client WHERE client_id = :client AND client_secret = PASSWORD(:secret)";
$stmtClient = $dbh->prepare($queryClient);
$stmtClient->bindParam(':client', $clientId);
$stmtClient->bindParam(':secret', $secret);
$stmtClient->execute();
$result = $stmtClient->fetchAll();
if (count($result) === 0) {
return null;
}
if (count($result) > 1) {
throw new \Exception("Found multiple clients associated to the same client id!");
}
$row = $result[0];
$client = new CliClient();
$client->id = $clientId;
if ($row['scope'] !== null) {
$client->scope = explode(' ', $row['scope']);
} else {
$client->scope = [];
}
if ($row['audience'] !== null) {
$client->audience = explode(' ', $row['audience']);
} else {
$client->audience = [];
}
return $client;
}
} }
...@@ -19,6 +19,7 @@ class AccessTokenData { ...@@ -19,6 +19,7 @@ class AccessTokenData {
public $redirectUri; public $redirectUri;
public $clientId; public $clientId;
public $scope; public $scope;
public $audience;
public function __construct() { public function __construct() {
$this->creationTime = time(); $this->creationTime = time();
......
<?php
namespace RAP;
class CliClient {
public $id;
public $scope;
public $audience;
}
...@@ -106,7 +106,10 @@ Flight::route('POST /auth/oauth2/token', function() { ...@@ -106,7 +106,10 @@ Flight::route('POST /auth/oauth2/token', function() {
switch ($params['grant_type']) { switch ($params['grant_type']) {
case "authorization_code": case "authorization_code":
$token = $requestHandler->handleAccessTokenRequest($params); $token = $requestHandler->handleGetTokenFromCodeRequest($params);
break;
case "client_credentials":
$token = $requestHandler->handleClientCredentialsRequest($params);
break; break;
case "refresh_token": case "refresh_token":
$token = $requestHandler->handleRefreshTokenRequest($params); $token = $requestHandler->handleRefreshTokenRequest($params);
......
...@@ -26,6 +26,14 @@ CREATE TABLE `oauth2_client_scope_audience_mapping` ( ...@@ -26,6 +26,14 @@ CREATE TABLE `oauth2_client_scope_audience_mapping` (
FOREIGN KEY (`client_id`) REFERENCES `oauth2_client`(`id`) FOREIGN KEY (`client_id`) REFERENCES `oauth2_client`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `cli_client` (
`client_id` varchar(255) NOT NULL,
`client_secret` varchar(255) NOT NULL,
`scope` text,
`audience` text,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` ( CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`primary_identity` bigint(20) DEFAULT NULL, `primary_identity` bigint(20) DEFAULT NULL,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment