Loading .gitignore +2 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,5 @@ vendor/ client-icons/ /nbproject/ *.pem /build/ .phpunit.result.cache README.md +6 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,12 @@ Create the logs directory and assign ownership to the Apache user (usually www-d mkdir logs sudo chown www-data logs ### Run Unit Tests and build code coverage report (XDebug or another code coverage driver needs to be installed; e.g. `sudo apt install php-xdebug`) ./vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-html build/coverage-report tests/ ## Additional information and developer guide See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home classes/IdTokenBuilder.php +6 −13 Original line number Diff line number Diff line Loading @@ -12,22 +12,13 @@ class IdTokenBuilder { $this->locator = $locator; } public function getIdToken(AccessToken $accessToken, string $alg): string { $head = array("alg" => $alg, "typ" => "JWT"); $header = base64_encode(json_encode($head)); public function getIdToken(AccessToken $accessToken): string { $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $payloadArr = $this->createPayloadArray($accessToken); $payloadArr['kid'] = $keyPair->keyId; $payload = base64_encode(json_encode($payloadArr)); $payload = $this->createPayloadArray($accessToken); $token_value = $header . "." . $payload; return JWT::encode($token_value, $keyPair->privateKey, $alg); return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } private function createPayloadArray(AccessToken $accessToken) { Loading @@ -48,8 +39,10 @@ class IdTokenBuilder { if (in_array("profile", $accessToken->scope)) { $payloadArr['given_name'] = $user->getName(); $payloadArr['family_name'] = $user->getSurname(); if ($user->getInstitution() !== null) { $payloadArr['org'] = $user->getInstitution(); } } return $payloadArr; } Loading classes/JWKSHandler.php +23 −11 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ class JWKSHandler { $rsa = new RSA(); $rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_PKCS1); $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS1); $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS8); $result = $rsa->createKey(); $keyPair = new RSAKeyPair(); Loading @@ -41,25 +41,37 @@ class JWKSHandler { $keyPairs = $dao->getRSAKeyPairs(); $jwks = []; $keys = []; foreach ($keyPairs as $keyPair) { $publicKey = str_replace("\n", "", $keyPair->publicKey); $publicKey = str_replace("\r", "", $publicKey); $publicKey = str_replace('-----BEGIN RSA PUBLIC KEY-----', '', $publicKey); $publicKey = str_replace('-----END RSA PUBLIC KEY-----', '', $publicKey); $rsa = new RSA(); $rsa->loadKey($keyPair->publicKey); $rsa->setPublicKey(); $publicKeyXML = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_XML); $rsaModulus = $this->getTagContent($publicKeyXML, "Modulus"); $rsaExponent = $this->getTagContent($publicKeyXML, "Exponent"); $jwk = []; $jwk['kty'] = "RSA"; $jwk['kid'] = $keyPair->id; $jwk['kid'] = $keyPair->keyId; $jwk['use'] = "sig"; $jwk['n'] = $publicKey; $jwk['e'] = "AQAB"; $jwk['n'] = $rsaModulus; $jwk['e'] = $rsaExponent; array_push($keys, $jwk); } array_push($jwks, $jwk); return [ "keys" => $keys ]; } return $jwks; private function getTagContent(string $publicKeyXML, string $tagname): string { $matches = []; $pattern = "#<\s*?$tagname\b[^>]*>(.*?)</$tagname\b[^>]*>#s"; preg_match($pattern, $publicKeyXML, $matches); return $matches[1]; } } classes/OAuth2RequestHandler.php +57 −58 Original line number Diff line number Diff line Loading @@ -10,44 +10,31 @@ class OAuth2RequestHandler { $this->locator = $locator; } public function handleAuthorizeRequest() { public function handleAuthorizeRequest($params) { if (!isset($_REQUEST['client_id'])) { if ($params['client_id'] === null) { throw new BadRequestException("Client id is required"); } if (!isset($_REQUEST['redirect_uri'])) { if ($params['redirect_uri'] === null) { throw new BadRequestException("Redirect URI is required"); } $clientId = $_REQUEST['client_id']; $redirectUrl = $_REQUEST['redirect_uri']; $client = $this->locator->getDAO()->getOAuth2ClientByClientId($clientId); $client = $this->locator->getDAO()->getOAuth2ClientByClientId($params['client_id']); if ($client === null) { throw new BadRequestException("Invalid client id: " . $clientId); throw new BadRequestException("Invalid client id: " . $params['client_id']); } if ($client->redirectUrl !== $redirectUrl) { throw new BadRequestException("Invalid client redirect URI: " . $redirectUrl); if ($client->redirectUrl !== $params['redirect_uri']) { throw new BadRequestException("Invalid client redirect URI: " . $params['redirect_uri']); } $alg; if (isset($_REQUEST['alg'])) { $alg = $_REQUEST['alg']; } else { $alg = $params['alg']; if ($alg === null) { $alg = "RS256"; } if (isset($_GET['code'])) { } else { $this->executeStateFlow($client); } } private function executeStateFlow(OAuth2Client $client) { if (!isset($_REQUEST['state'])) { $state = $params['state']; if ($state === null) { throw new BadRequestException("State is required"); } Loading @@ -55,7 +42,12 @@ class OAuth2RequestHandler { $oauth2Data = new \RAP\OAuth2Data(); $oauth2Data->clientId = $client->client; $oauth2Data->redirectUrl = $client->redirectUrl; $oauth2Data->state = $_REQUEST['state']; $oauth2Data->state = $state; $scope = $params['scope']; if ($scope !== null) { $oauth2Data->scope = explode(' ', $scope); } $session = $this->locator->getSession(); $session->setOAuth2Data($oauth2Data); Loading @@ -71,7 +63,7 @@ class OAuth2RequestHandler { $accessToken->userId = $session->user->id; $accessToken->clientId = $session->getOAuth2Data()->clientId; $accessToken->redirectUri = $session->getOAuth2Data()->redirectUrl; //$accessToken->scope = $accessToken->scope = $session->getOAuth2Data()->scope; $this->locator->getAccessTokenDAO()->createAccessToken($accessToken); Loading @@ -83,46 +75,48 @@ class OAuth2RequestHandler { return $redirectUrl; } public function handleAccessTokenRequest(): array { public function handleAccessTokenRequest($params): array { $this->validateAccessTokenRequest(); $this->validateAccessTokenRequest($params); $code = filter_input(INPUT_POST, 'code', FILTER_SANITIZE_STRING); $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($code); $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($params['code']); if ($accessToken === null) { throw new BadRequestException("No token for given code"); } $this->validateParametersMatching(); if ($accessToken->redirectUri !== $params['redirect_uri']) { throw new BadRequestException("Invalid redirect URI: " . $params['redirect_uri']); } $token = []; $token['access_token'] = $accessToken->token; //$token['access_token'] = $accessToken->token; $token['token_type'] = 'bearer'; $token['expires_in'] = 300; error_log($accessToken->creationTime); error_log($accessToken->expirationTime); $token['expires_in'] = $this->getExpiresIn($accessToken); if ($accessToken->scope !== null) { $token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken->userId, 'RS256'); $token['access_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); //$token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); } else { $token['access_token'] = $accessToken->token; } return $token; } private function validateAccessTokenRequest() { private function validateAccessTokenRequest($params) { if (!isset($_POST['grant_type'])) { throw new BadRequestException("Client id is required"); } else if ($_POST['grant_type'] !== 'authorization_code') { if ($params['grant_type'] === null) { throw new BadRequestException("grant_type is required"); } else if ($params['grant_type'] !== 'authorization_code') { throw new BadRequestException("grant_type must be authorization_code"); } if (!isset($_POST['code'])) { throw new BadRequestException("Client id is required"); if ($params['code'] === null) { throw new BadRequestException("code id is required"); } if (!isset($_POST['redirect_uri'])) { if ($params['redirect_uri'] === null) { throw new BadRequestException("Redirect URI is required"); } Loading @@ -130,27 +124,32 @@ class OAuth2RequestHandler { // however some clients don't send it } private function validateParametersMatching() { } public function handleCheckTokenRequest(): array { public function handleCheckTokenRequest($token): array { if (!isset($_POST['token'])) { throw new BadRequestException("Access token id is required"); } $accessToken = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING); //if($accessToken) $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($token); $user = $this->locator->getDAO()->findUserById($accessToken->userId); $result = []; $result['exp'] = 3600; $result['user_name'] = "test"; $result['client_id'] = "gms"; $result['scope'] = "profile"; $result['exp'] = $this->getExpiresIn($accessToken); $result['user_name'] = $user->id; $result['client_id'] = $accessToken->clientId; if ($accessToken->scope !== null) { $result['scope'] = $accessToken->scope; $result['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); } return $result; } private function getExpiresIn(AccessToken $accessToken) { $expTime = strtotime($accessToken->expirationTime); $now = time(); return $expTime - $now; } } Loading
.gitignore +2 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,5 @@ vendor/ client-icons/ /nbproject/ *.pem /build/ .phpunit.result.cache
README.md +6 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,12 @@ Create the logs directory and assign ownership to the Apache user (usually www-d mkdir logs sudo chown www-data logs ### Run Unit Tests and build code coverage report (XDebug or another code coverage driver needs to be installed; e.g. `sudo apt install php-xdebug`) ./vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-html build/coverage-report tests/ ## Additional information and developer guide See the wiki: https://www.ict.inaf.it/gitlab/zorba/rap-ia2/wikis/home
classes/IdTokenBuilder.php +6 −13 Original line number Diff line number Diff line Loading @@ -12,22 +12,13 @@ class IdTokenBuilder { $this->locator = $locator; } public function getIdToken(AccessToken $accessToken, string $alg): string { $head = array("alg" => $alg, "typ" => "JWT"); $header = base64_encode(json_encode($head)); public function getIdToken(AccessToken $accessToken): string { $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $payloadArr = $this->createPayloadArray($accessToken); $payloadArr['kid'] = $keyPair->keyId; $payload = base64_encode(json_encode($payloadArr)); $payload = $this->createPayloadArray($accessToken); $token_value = $header . "." . $payload; return JWT::encode($token_value, $keyPair->privateKey, $alg); return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } private function createPayloadArray(AccessToken $accessToken) { Loading @@ -48,8 +39,10 @@ class IdTokenBuilder { if (in_array("profile", $accessToken->scope)) { $payloadArr['given_name'] = $user->getName(); $payloadArr['family_name'] = $user->getSurname(); if ($user->getInstitution() !== null) { $payloadArr['org'] = $user->getInstitution(); } } return $payloadArr; } Loading
classes/JWKSHandler.php +23 −11 Original line number Diff line number Diff line Loading @@ -20,7 +20,7 @@ class JWKSHandler { $rsa = new RSA(); $rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_PKCS1); $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS1); $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS8); $result = $rsa->createKey(); $keyPair = new RSAKeyPair(); Loading @@ -41,25 +41,37 @@ class JWKSHandler { $keyPairs = $dao->getRSAKeyPairs(); $jwks = []; $keys = []; foreach ($keyPairs as $keyPair) { $publicKey = str_replace("\n", "", $keyPair->publicKey); $publicKey = str_replace("\r", "", $publicKey); $publicKey = str_replace('-----BEGIN RSA PUBLIC KEY-----', '', $publicKey); $publicKey = str_replace('-----END RSA PUBLIC KEY-----', '', $publicKey); $rsa = new RSA(); $rsa->loadKey($keyPair->publicKey); $rsa->setPublicKey(); $publicKeyXML = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_XML); $rsaModulus = $this->getTagContent($publicKeyXML, "Modulus"); $rsaExponent = $this->getTagContent($publicKeyXML, "Exponent"); $jwk = []; $jwk['kty'] = "RSA"; $jwk['kid'] = $keyPair->id; $jwk['kid'] = $keyPair->keyId; $jwk['use'] = "sig"; $jwk['n'] = $publicKey; $jwk['e'] = "AQAB"; $jwk['n'] = $rsaModulus; $jwk['e'] = $rsaExponent; array_push($keys, $jwk); } array_push($jwks, $jwk); return [ "keys" => $keys ]; } return $jwks; private function getTagContent(string $publicKeyXML, string $tagname): string { $matches = []; $pattern = "#<\s*?$tagname\b[^>]*>(.*?)</$tagname\b[^>]*>#s"; preg_match($pattern, $publicKeyXML, $matches); return $matches[1]; } }
classes/OAuth2RequestHandler.php +57 −58 Original line number Diff line number Diff line Loading @@ -10,44 +10,31 @@ class OAuth2RequestHandler { $this->locator = $locator; } public function handleAuthorizeRequest() { public function handleAuthorizeRequest($params) { if (!isset($_REQUEST['client_id'])) { if ($params['client_id'] === null) { throw new BadRequestException("Client id is required"); } if (!isset($_REQUEST['redirect_uri'])) { if ($params['redirect_uri'] === null) { throw new BadRequestException("Redirect URI is required"); } $clientId = $_REQUEST['client_id']; $redirectUrl = $_REQUEST['redirect_uri']; $client = $this->locator->getDAO()->getOAuth2ClientByClientId($clientId); $client = $this->locator->getDAO()->getOAuth2ClientByClientId($params['client_id']); if ($client === null) { throw new BadRequestException("Invalid client id: " . $clientId); throw new BadRequestException("Invalid client id: " . $params['client_id']); } if ($client->redirectUrl !== $redirectUrl) { throw new BadRequestException("Invalid client redirect URI: " . $redirectUrl); if ($client->redirectUrl !== $params['redirect_uri']) { throw new BadRequestException("Invalid client redirect URI: " . $params['redirect_uri']); } $alg; if (isset($_REQUEST['alg'])) { $alg = $_REQUEST['alg']; } else { $alg = $params['alg']; if ($alg === null) { $alg = "RS256"; } if (isset($_GET['code'])) { } else { $this->executeStateFlow($client); } } private function executeStateFlow(OAuth2Client $client) { if (!isset($_REQUEST['state'])) { $state = $params['state']; if ($state === null) { throw new BadRequestException("State is required"); } Loading @@ -55,7 +42,12 @@ class OAuth2RequestHandler { $oauth2Data = new \RAP\OAuth2Data(); $oauth2Data->clientId = $client->client; $oauth2Data->redirectUrl = $client->redirectUrl; $oauth2Data->state = $_REQUEST['state']; $oauth2Data->state = $state; $scope = $params['scope']; if ($scope !== null) { $oauth2Data->scope = explode(' ', $scope); } $session = $this->locator->getSession(); $session->setOAuth2Data($oauth2Data); Loading @@ -71,7 +63,7 @@ class OAuth2RequestHandler { $accessToken->userId = $session->user->id; $accessToken->clientId = $session->getOAuth2Data()->clientId; $accessToken->redirectUri = $session->getOAuth2Data()->redirectUrl; //$accessToken->scope = $accessToken->scope = $session->getOAuth2Data()->scope; $this->locator->getAccessTokenDAO()->createAccessToken($accessToken); Loading @@ -83,46 +75,48 @@ class OAuth2RequestHandler { return $redirectUrl; } public function handleAccessTokenRequest(): array { public function handleAccessTokenRequest($params): array { $this->validateAccessTokenRequest(); $this->validateAccessTokenRequest($params); $code = filter_input(INPUT_POST, 'code', FILTER_SANITIZE_STRING); $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($code); $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($params['code']); if ($accessToken === null) { throw new BadRequestException("No token for given code"); } $this->validateParametersMatching(); if ($accessToken->redirectUri !== $params['redirect_uri']) { throw new BadRequestException("Invalid redirect URI: " . $params['redirect_uri']); } $token = []; $token['access_token'] = $accessToken->token; //$token['access_token'] = $accessToken->token; $token['token_type'] = 'bearer'; $token['expires_in'] = 300; error_log($accessToken->creationTime); error_log($accessToken->expirationTime); $token['expires_in'] = $this->getExpiresIn($accessToken); if ($accessToken->scope !== null) { $token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken->userId, 'RS256'); $token['access_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); //$token['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); } else { $token['access_token'] = $accessToken->token; } return $token; } private function validateAccessTokenRequest() { private function validateAccessTokenRequest($params) { if (!isset($_POST['grant_type'])) { throw new BadRequestException("Client id is required"); } else if ($_POST['grant_type'] !== 'authorization_code') { if ($params['grant_type'] === null) { throw new BadRequestException("grant_type is required"); } else if ($params['grant_type'] !== 'authorization_code') { throw new BadRequestException("grant_type must be authorization_code"); } if (!isset($_POST['code'])) { throw new BadRequestException("Client id is required"); if ($params['code'] === null) { throw new BadRequestException("code id is required"); } if (!isset($_POST['redirect_uri'])) { if ($params['redirect_uri'] === null) { throw new BadRequestException("Redirect URI is required"); } Loading @@ -130,27 +124,32 @@ class OAuth2RequestHandler { // however some clients don't send it } private function validateParametersMatching() { } public function handleCheckTokenRequest(): array { public function handleCheckTokenRequest($token): array { if (!isset($_POST['token'])) { throw new BadRequestException("Access token id is required"); } $accessToken = filter_input(INPUT_POST, 'token', FILTER_SANITIZE_STRING); //if($accessToken) $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($token); $user = $this->locator->getDAO()->findUserById($accessToken->userId); $result = []; $result['exp'] = 3600; $result['user_name'] = "test"; $result['client_id'] = "gms"; $result['scope'] = "profile"; $result['exp'] = $this->getExpiresIn($accessToken); $result['user_name'] = $user->id; $result['client_id'] = $accessToken->clientId; if ($accessToken->scope !== null) { $result['scope'] = $accessToken->scope; $result['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken); } return $result; } private function getExpiresIn(AccessToken $accessToken) { $expTime = strtotime($accessToken->expirationTime); $now = time(); return $expTime - $now; } }