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

Refactoring and unit tests

parent 052143cb
Branches
No related tags found
No related merge requests found
......@@ -15,19 +15,19 @@ class ClientAuthChecker {
$this->locator = $locator;
}
public function validateClientAuth(): void {
public function validateClientAuth(array $headers): BrowserBasedOAuth2Client {
$basic = $this->getBasicAuthArray();
$basic = $this->getBasicAuthArray($headers);
$clientId = $basic[0];
$clientSecret = $basic[1];
$this->locator->getBrowserBasedOAuth2ClientByIdAndSecret($clientId, $clientSecret);
return $this->locator->getBrowserBasedOAuth2ClientByIdAndSecret($clientId, $clientSecret);
}
public function validateCliClientAuth(): CliOAuth2Client {
public function validateCliClientAuth(array $headers): CliOAuth2Client {
$basic = $this->getBasicAuthArray();
$basic = $this->getBasicAuthArray($headers);
$clientId = $basic[0];
$clientSecret = $basic[1];
......@@ -35,8 +35,7 @@ class ClientAuthChecker {
return $this->locator->getCliClientByIdAndSecret($clientId, $clientSecret);
}
private function getBasicAuthArray(): array {
$headers = apache_request_headers();
private function getBasicAuthArray($headers): array {
if (!isset($headers['Authorization'])) {
throw new UnauthorizedException("Missing Authorization header");
......
......@@ -31,7 +31,7 @@ trait ClientsLocator {
private function getClientConfigFromListByIdAndSecret(array $clients, string $clientId, string $secret): object {
$client = $this->getClientConfigFromListById($clients, $clientId);
$secretHash = hash('sha256', $secret);
if ($client->secretHash !== $secretHash) {
if ($client->secret !== $secretHash) {
throw new UnauthorizedException("Wrong secret provided for client '$clientId'");
}
return $client;
......
......@@ -79,8 +79,10 @@ class JWKSHandler {
public function loadAllJWKS(): array {
foreach ($this->locator->config->jwksUrls as $url) {
$this->loadJWKS($url);
foreach ($this->locator->getBrowserBasedOAuth2Clients() as $client) {
if ($client->jwks !== null) {
$this->loadJWKS($client->jwks);
}
}
$dao = $this->locator->getJWKSDAO();
......
......@@ -74,18 +74,10 @@ class Locator {
}
}
public function getCallbackHandler(): CallbackHandler {
return new CallbackHandler($this);
}
public function getUserHandler(): UserHandler {
return new UserHandler($this);
}
public function getMailSender(): MailSender {
return new MailSender($_SERVER['HTTP_HOST'], $this->getBasePath());
}
public function getOAuth2RequestHandler(): OAuth2RequestHandler {
return new OAuth2RequestHandler($this);
}
......
......@@ -76,7 +76,12 @@ class OAuth2RequestHandler {
if ($state !== null) {
// Authorization code grant flow
$redirectUrl = $session->getOAuth2RequestData()->redirectUrl
. '?code=' . $code . '&scope=profile&state=' . $state;
. '?code=' . $code;
$scope = $tokenData->scope;
if ($scope !== null && count($scope) > 0) {
$redirectUrl .= '&scope=' . implode("%20", $scope);
}
$redirectUrl .= '&state=' . $state;
} else {
// Implicit grant flow
$idToken = $this->locator->getTokenBuilder()->getIdToken($tokenData, function(& $jwt) use($nonce) {
......@@ -88,9 +93,27 @@ class OAuth2RequestHandler {
return $redirectUrl;
}
public function handleGetTokenFromCodeRequest($params): array {
public function handleAccessTokenRequest(array $params, array $headers): array {
if ($params['grant_type'] === null) {
throw new \RAP\BadRequestException("grant_type is required");
}
switch ($params['grant_type']) {
case "authorization_code":
return $this->handleGetTokenFromCodeRequest($params, $headers);
case "client_credentials":
return $this->handleClientCredentialsRequest($headers);
case "refresh_token":
return $this->handleRefreshTokenRequest($params, $headers);
default:
throw new \RAP\BadRequestException("Unsupported grant type " . $params['grant_type']);
}
}
private function handleGetTokenFromCodeRequest(array $params, array $headers): array {
$this->locator->getClientAuthChecker()->validateClientAuth();
$this->locator->getClientAuthChecker()->validateClientAuth($headers);
if ($params['code'] === null) {
throw new BadRequestException("code id is required");
......@@ -120,9 +143,9 @@ class OAuth2RequestHandler {
return $response;
}
public function handleClientCredentialsRequest($params): array {
private function handleClientCredentialsRequest(array $headers): array {
$client = $this->locator->getClientAuthChecker()->validateCliClientAuth();
$client = $this->locator->getClientAuthChecker()->validateCliClientAuth($headers);
$accessTokenData = new AccessTokenData();
$accessTokenData->clientId = $client->id;
......@@ -133,9 +156,9 @@ class OAuth2RequestHandler {
return $this->getAccessTokenResponse($accessTokenData, false);
}
public function handleRefreshTokenRequest($params): array {
private function handleRefreshTokenRequest(array $params, array $headers): array {
$this->locator->getClientAuthChecker()->validateClientAuth();
$this->locator->getClientAuthChecker()->validateClientAuth($headers);
if ($params['refresh_token'] === null) {
throw new BadRequestException("refresh_token is required");
......
......@@ -53,7 +53,7 @@ class TokenBuilder {
return $payloadArr;
}
public function getAccessToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null) {
public function getAccessToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string {
$keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
......@@ -95,6 +95,7 @@ class TokenBuilder {
$audiences = [$tokenData->clientId];
if ($client->scopeAudienceMap !== null) {
foreach ($tokenData->scope as $scope) {
if (array_key_exists($scope, $client->scopeAudienceMap)) {
$audience = $client->scopeAudienceMap[$scope];
......@@ -103,6 +104,7 @@ class TokenBuilder {
}
}
}
}
return $this->getAudienceClaim($audiences);
}
......
......@@ -52,7 +52,7 @@ tokenIssuer:
clients:
- label: "GMS Test (localhost)"
id: gms
secret: "XXXXXX"
secret: 2a97516c354b68848cdbd8f54a226a0a55b21ed138e207ad6c5cbb9c00aa5aea
redirect: http://localhost:8082/gms/login
scope: "openid email"
home: http://localhost:8082/gms
......@@ -62,7 +62,7 @@ clients:
jwks:
- label: "Asiago Astrophysical Observatory (localhost)"
id: aao-dev
secret: "XXXXXX"
secret: 2a97516c354b68848cdbd8f54a226a0a55b21ed138e207ad6c5cbb9c00aa5aea
redirect: http://localhost:8081/aao/login
scope: "openid read:userspace write:userspace read:fileserver write:fileserver read:gms"
home: http://localhost:8081/aao
......@@ -72,6 +72,6 @@ clients:
jwks: http://localhost:8081/aao/jwks
cliClients:
- id: gms_cli
secret: "XXXXXX"
secret: 2a97516c354b68848cdbd8f54a226a0a55b21ed138e207ad6c5cbb9c00aa5aea
scope: "read:gms write:gms read:rap"
audience: gms
\ No newline at end of file
......@@ -102,25 +102,10 @@ Flight::route('POST /auth/oauth2/token', function() {
"scope" => filter_input(INPUT_POST, "scope", FILTER_SANITIZE_STRING)
];
if ($params['grant_type'] === null) {
throw new \RAP\BadRequestException("grant_type is required");
}
$headers = apache_request_headers();
$requestHandler = new \RAP\OAuth2RequestHandler($locator);
switch ($params['grant_type']) {
case "authorization_code":
$token = $requestHandler->handleGetTokenFromCodeRequest($params);
break;
case "client_credentials":
$token = $requestHandler->handleClientCredentialsRequest($params);
break;
case "refresh_token":
$token = $requestHandler->handleRefreshTokenRequest($params);
break;
default:
throw new \RAP\BadRequestException("Unsupported grant type " . $params['grant_type']);
}
$token = $requestHandler->handleAccessTokenRequest($params, $headers);
Flight::json($token);
});
......
<?php
use PHPUnit\Framework\TestCase;
class ClientAuthCheckerTest extends TestCase {
public function testValidateClientAuth() {
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->expects($this->once())
->method('getBrowserBasedOAuth2ClientByIdAndSecret')->with('client', 'secret');
$authChecker = new \RAP\ClientAuthChecker($locatorStub);
$authChecker->validateClientAuth([
"Authorization" => "Basic " . base64_encode("client:secret")
]);
}
public function testValidateCliClientAuth() {
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->expects($this->once())
->method('getCliClientByIdAndSecret')->with('client', 'secret');
$authChecker = new \RAP\ClientAuthChecker($locatorStub);
$authChecker->validateCliClientAuth([
"Authorization" => "Basic " . base64_encode("client:secret")
]);
}
}
<?php
use PHPUnit\Framework\TestCase;
class LocatorTest extends TestCase {
public function testLocator() {
define('ROOT', dirname(dirname(__FILE__)));
$config = yaml_parse_file(ROOT . '/config-example.yaml');
$config = json_decode(json_encode($config), FALSE);
$locator = new \RAP\Locator($config);
$this->assertNotNull($locator->getUserDAO());
$this->assertNotNull($locator->getJWKSDAO());
$this->assertNotNull($locator->getAccessTokenDAO());
$this->assertNotNull($locator->getRefreshTokenDAO());
$this->assertNotNull($locator->getUserHandler());
$this->assertNotNull($locator->getOAuth2RequestHandler());
$this->assertNotNull($locator->getTokenBuilder());
$this->assertNotNull($locator->getTokenChecker());
$this->assertNotNull($locator->getClientAuthChecker());
$this->assertNotNull($locator->getSession());
$this->assertNotNull($locator->getServiceLogger());
$this->assertNotNull($locator->getAuditLogger());
$this->assertNotNull($locator->getJWKSHandler());
$this->assertNotNull($locator->getBrowserBasedOAuth2Clients());
$this->assertNotNull($locator->getBrowserBasedOAuth2ClientById("gms", false));
$this->assertNotNull($locator->getBrowserBasedOAuth2ClientByIdAndSecret("gms", "demo"));
$this->assertNotNull($locator->getCliClientByIdAndSecret("gms_cli", "demo"));
}
}
......@@ -105,4 +105,151 @@ final class OAuth2RequestHandlerTest extends TestCase {
$this->assertEquals('id-token', $result['id_token']);
}
public function testHandleGetTokenFromCodeRequest(): void {
$authCheckerStub = $this->createMock(\RAP\ClientAuthChecker::class);
$tokenData = new \RAP\AccessTokenData();
$tokenData->redirectUri = "redirect";
$tokenData->scope = ['openid'];
$tokenDaoStub = $this->createMock(\RAP\AccessTokenDAO::class);
$tokenDaoStub->method('retrieveTokenDataFromCode')->willReturn($tokenData);
$tokenBuilderStub = $this->createMock(\RAP\TokenBuilder::class);
$tokenBuilderStub->method('getAccessToken')->willReturn('<access_token>');
$refreshTokenDaoStub = $this->createMock(\RAP\RefreshTokenDAO::class);
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->method('getClientAuthChecker')->willReturn($authCheckerStub);
$locatorStub->method('getAccessTokenDAO')->willReturn($tokenDaoStub);
$locatorStub->method('getTokenBuilder')->willReturn($tokenBuilderStub);
$locatorStub->method('getRefreshTokenDAO')->willReturn($refreshTokenDaoStub);
$authCheckerStub->expects($this->once())
->method('validateClientAuth')->with($this->anything());
$tokenDaoStub->expects($this->once())
->method('retrieveTokenDataFromCode')->with($this->anything());
$tokenDaoStub->expects($this->once())
->method('deleteTokenData')->with($this->anything());
$requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
$params = [
"grant_type" => "authorization_code",
"redirect_uri" => "redirect",
"code" => "123"
];
$result = $requestHandler->handleAccessTokenRequest($params, []);
$this->assertEquals(3600, $result['expires_in']);
$this->assertEquals("Bearer", $result['token_type']);
$this->assertEquals("<access_token>", $result['access_token']);
$this->assertNotNull($result['refresh_token']);
}
public function testHandleClientCredentialsRequest(): void {
$authCheckerStub = $this->createMock(\RAP\ClientAuthChecker::class);
$tokenBuilderStub = $this->createMock(\RAP\TokenBuilder::class);
$tokenBuilderStub->method('getAccessToken')->willReturn('<access_token>');
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->method('getClientAuthChecker')->willReturn($authCheckerStub);
$locatorStub->method('getTokenBuilder')->willReturn($tokenBuilderStub);
$authCheckerStub->expects($this->once())
->method('validateCliClientAuth')->with($this->anything());
$requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
$params = [
"grant_type" => "client_credentials"
];
$result = $requestHandler->handleAccessTokenRequest($params, []);
$this->assertEquals(3600, $result['expires_in']);
$this->assertEquals("Bearer", $result['token_type']);
$this->assertEquals("<access_token>", $result['access_token']);
$this->assertFalse(isset($result['refresh_token']));
}
public function testHandleRefreshTokenRequest(): void {
$refreshTokenData = new \RAP\RefreshTokenData();
$refreshTokenData->scope = ['openid', 'email'];
$authCheckerStub = $this->createMock(\RAP\ClientAuthChecker::class);
$tokenBuilderStub = $this->createMock(\RAP\TokenBuilder::class);
$tokenBuilderStub->method('getAccessToken')->willReturn('<access_token>');
$refreshTokenDaoStub = $this->createMock(\RAP\RefreshTokenDAO::class);
$refreshTokenDaoStub->method('getRefreshTokenData')->willReturn($refreshTokenData);
$tokenDaoStub = $this->createMock(\RAP\AccessTokenDAO::class);
$tokenDaoStub->method('createTokenData')->will($this->returnArgument(0));
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->method('getClientAuthChecker')->willReturn($authCheckerStub);
$locatorStub->method('getTokenBuilder')->willReturn($tokenBuilderStub);
$locatorStub->method('getRefreshTokenDAO')->willReturn($refreshTokenDaoStub);
$locatorStub->method('getAccessTokenDAO')->willReturn($tokenDaoStub);
$authCheckerStub->expects($this->once())
->method('validateClientAuth')->with($this->anything());
$requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
$params = [
"grant_type" => "refresh_token",
"refresh_token" => "<refresh_token>",
"scope" => "openid email"
];
$result = $requestHandler->handleAccessTokenRequest($params, []);
$this->assertEquals(3600, $result['expires_in']);
$this->assertEquals("Bearer", $result['token_type']);
$this->assertEquals("<access_token>", $result['access_token']);
$this->assertNotNull($result['refresh_token']);
}
public function testGetRedirectResponseUrlForAuthorizationCodeFlow(): void {
$user = new \RAP\User();
$user->id = "123";
$requestData = new \RAP\OAuth2RequestData();
$requestData->clientId = "<client-id>";
$requestData->redirectUrl = "<base-path>";
$requestData->scope = ["openid", "profile"];
$requestData->state = "<state>";
$sessionStub = $this->createMock(\RAP\SessionData::class);
$sessionStub->method('getUser')->willReturn($user);
$sessionStub->method('getOAuth2RequestData')->willReturn($requestData);
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->method('getSession')->willReturn($sessionStub);
$requestHandler = new \RAP\OAuth2RequestHandler($locatorStub);
$result = $requestHandler->getRedirectResponseUrl();
$url_components = parse_url($result);
$this->assertEquals('<base-path>', $url_components['path']);
$params = [];
parse_str($url_components['query'], $params);
$this->assertEquals("<state>", $params['state']);
$this->assertEquals("openid profile", $params['scope']);
$this->assertNotNull($params['code']);
}
}
......@@ -17,15 +17,7 @@ final class TokenBuilderTest extends TestCase {
$jwksDAOStub->method('getNewestKeyPair')->willReturn($keyPair);
$user = new \RAP\User();
$user->id = "user_id";
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->email = "name@inaf.it";
$identity->name = "Name";
$identity->surname = "Surname";
$identity->primary = true;
$identity->institution = "INAF";
$user->addIdentity($identity);
$user = $this->getUser();
$daoStub = $this->createMock(\RAP\UserDAO::class);
$locatorStub->method('getUserDAO')->willReturn($daoStub);
......@@ -48,9 +40,67 @@ final class TokenBuilderTest extends TestCase {
$this->assertEquals("issuer", $payload->iss);
$this->assertEquals($user->id, $payload->sub);
$this->assertEquals($user->getCompleteName(), $payload->name);
$identity = $user->identities[0];
$this->assertEquals($identity->name, $payload->given_name);
$this->assertEquals($identity->surname, $payload->family_name);
$this->assertEquals($identity->institution, $payload->org);
}
public function testGetAccessToken(): void {
$jwksDAOStub = $this->createMock(\RAP\JWKSDAO::class);
$locatorStub = $this->createMock(\RAP\Locator::class);
$locatorStub->method('getJWKSDAO')->willReturn($jwksDAOStub);
$jwksHandler = new \RAP\JWKSHandler($locatorStub);
$keyPair = $jwksHandler->generateKeyPair();
$jwksDAOStub->method('getNewestKeyPair')->willReturn($keyPair);
$userDaoStub = $this->createMock(\RAP\UserDAO::class);
$locatorStub->method('getUserDAO')->willReturn($userDaoStub);
$user = $this->getUser();
$userDaoStub->method('findUserById')->willReturn($user);
$client = new \RAP\BrowserBasedOAuth2Client((object) [
"id" => "aao",
"secret" => "2a97516c354b68848cdbd8f54a226a0a55b21ed138e207ad6c5cbb9c00aa5aea",
"redirect" => "redirect",
"scope" => "openid email",
"methods" => [],
"scopeAudienceMap" => ["read:gms" => "gms"]
]);
$locatorStub->method('getBrowserBasedOAuth2ClientById')->willReturn($client);
$locatorStub->config = json_decode('{"jwtIssuer": "issuer"}');
$tokenBuilder = new \RAP\TokenBuilder($locatorStub);
$tokenData = new \RAP\AccessTokenData();
$tokenData->token = "ttt";
$tokenData->scope = ["openid", "read:gms"];
$tokenData->userId = "user_id";
$tokenData->clientId = "aao";
$jwt = $tokenBuilder->getAccessToken($tokenData);
$this->assertNotNull($jwt);
}
private function getUser(): \RAP\User {
$user = new \RAP\User();
$user->id = "user_id";
$identity = new \RAP\Identity(\RAP\Identity::EDU_GAIN);
$identity->email = "name@inaf.it";
$identity->name = "Name";
$identity->surname = "Surname";
$identity->primary = true;
$identity->institution = "INAF";
$user->addIdentity($identity);
return $user;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment