Commit 66d0f69b authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added support for Refresh Token

parent ec3ca777
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ class IdTokenBuilder {
        $this->locator = $locator;
    }

    public function getIdToken(AccessToken $accessToken, $nonce = null): string {
    public function getIdToken(AccessToken $accessToken, string $nonce = null): string {

        $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();

@@ -21,15 +21,15 @@ class IdTokenBuilder {
        return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
    }

    private function createPayloadArray(AccessToken $accessToken, $nonce = null) {
    private function createPayloadArray(AccessToken $accessToken, string $nonce = null) {

        $user = $this->locator->getUserDAO()->findUserById($accessToken->userId);

        $payloadArr = array(
            'iss' => $this->locator->config->jwtIssuer,
            'sub' => $user->id,
            'iat' => time(),
            'exp' => time() + 3600,
            'iat' => $accessToken->creationTime,
            'exp' => $accessToken->expirationTime,
            'name' => $user->getCompleteName(),
            'aud' => $accessToken->clientId
        );
+10 −0
Original line number Diff line number Diff line
@@ -72,6 +72,16 @@ class Locator {
        }
    }

    public function getRefreshTokenDAO(): RefreshTokenDAO {
        $databaseConfig = $this->config->databaseConfig;
        switch ($databaseConfig->dbtype) {
            case 'MySQL':
                return new MySQLRefreshTokenDAO($this);
            default:
                throw new \Exception($databaseConfig->dbtype . ' not supported yet');
        }
    }

    public function getCallbackHandler(): CallbackHandler {
        return new CallbackHandler($this);
    }
+82 −17
Original line number Diff line number Diff line
@@ -90,7 +90,16 @@ class OAuth2RequestHandler {

    public function handleAccessTokenRequest($params): array {

        $this->validateAccessTokenRequest($params);
        if ($params['code'] === null) {
            throw new BadRequestException("code id is required");
        }

        if ($params['redirect_uri'] === null) {
            throw new BadRequestException("Redirect URI is required");
        }

        // Note: theorically the standard wants also the client_id here,
        // however some clients don't send it

        $accessToken = $this->locator->getAccessTokenDAO()->retrieveAccessTokenFromCode($params['code']);

@@ -102,10 +111,73 @@ class OAuth2RequestHandler {
            throw new BadRequestException("Invalid redirect URI: " . $params['redirect_uri']);
        }

        return $this->getAccessTokenResponse($accessToken);
    }

    public function handleRefreshTokenRequest($params): array {

        if ($params['refresh_token'] === null) {
            throw new BadRequestException("refresh_token is required");
        }

        $refreshToken = $this->locator->getRefreshTokenDAO()->getRefreshToken($params['refresh_token']);

        if ($refreshToken === null || $refreshToken->isExpired()) {
            throw new UnauthorizedException("Invalid refresh token");
        }

        $scope = $this->getScope($params, $refreshToken);

        // Generating a new access token
        $accessToken = new AccessToken();
        $accessToken->token = base64_encode(bin2hex(openssl_random_pseudo_bytes(128)));
        $accessToken->clientId = $refreshToken->clientId;
        $accessToken->userId = $refreshToken->userId;
        $accessToken->scope = $scope;

        $accessToken = $this->locator->getAccessTokenDAO()->createAccessToken($accessToken);

        return $this->getAccessTokenResponse($accessToken);
    }

    /**
     * We can request a new access token with a scope that is a subset (or the
     * same set) of the scope defined for the refresh token.
     */
    private function getScope(array $params, RefreshToken $refreshToken): ?array {

        $scope = $refreshToken->scope;

        if ($params['scope'] !== null) {

            $newScopeValues = explode(' ', $params['scope']);

            foreach ($newScopeValues as $newScopeValue) {
                $found = false;
                foreach ($scope as $oldScopeValue) {
                    if ($oldScopeValue === $newScopeValue) {
                        $found = true;
                        break;
                    }
                }
                if (!$found) {
                    throw new BadRequestException("Scope " . $newScopeValue . " was not defined for the given refresh token");
                }
            }

            $scope = $newScopeValues;
        }

        return $scope;
    }

    private function getAccessTokenResponse(AccessToken $accessToken) {

        $result = [];
        $result['access_token'] = $accessToken->token;
        $result['token_type'] = 'Bearer';
        $result['expires_in'] = $accessToken->expirationTime - time();
        $result['refresh_token'] = $this->getNewRefreshToken($accessToken);

        if ($accessToken->scope !== null && in_array('openid', $accessToken->scope)) {
            $result['id_token'] = $this->locator->getIdTokenBuilder()->getIdToken($accessToken);
@@ -114,24 +186,17 @@ class OAuth2RequestHandler {
        return $result;
    }

    private function validateAccessTokenRequest($params) {

        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");
        }
    private function getNewRefreshToken(AccessToken $accessToken): string {

        if ($params['code'] === null) {
            throw new BadRequestException("code id is required");
        }
        $refreshToken = new RefreshToken();
        $refreshToken->token = base64_encode(bin2hex(openssl_random_pseudo_bytes(128)));
        $refreshToken->clientId = $accessToken->clientId;
        $refreshToken->userId = $accessToken->userId;
        $refreshToken->scope = $accessToken->scope;

        if ($params['redirect_uri'] === null) {
            throw new BadRequestException("Redirect URI is required");
        }
        $this->locator->getRefreshTokenDAO()->createRefreshToken($refreshToken);

        // Note: theorically the standard wants also the client_id here,
        // however some clients don't send it
        return $refreshToken->token;
    }

    public function handleCheckTokenRequest($token): array {
@@ -171,7 +236,7 @@ class OAuth2RequestHandler {
        $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($bearer_token);
        if ($accessToken === null) {
            $this->attemptJWTTokenValidation($bearer_token);
        } else if ($accessToken->expired) {
        } else if ($accessToken->isExpired()) {
            throw new UnauthorizedException("Access token is expired");
        }
    }
+0 −6
Original line number Diff line number Diff line
@@ -15,11 +15,5 @@ interface AccessTokenDAO {

    function getAccessToken(string $token): ?AccessToken;

    /**
     * Delete an access token from the database. This happens when the caller
     * application has received the token and used it for retrieving user
     * information from the token using the RAP REST web service.
     * @param type $token login token
     */
    function deleteAccessToken(string $token): void;
}
+12 −0
Original line number Diff line number Diff line
<?php

namespace RAP;

interface RefreshTokenDAO {

    function createRefreshToken(RefreshToken $refreshToken): RefreshToken;

    function getRefreshToken(string $token): ?RefreshToken;

    function deleteRefreshToken(string $token): void;
}
Loading