Skip to content
Snippets Groups Projects
Select Git revision
  • 3c3737cc4fc0962b205dc7054d6775bceec9751a
  • master default
  • rocky-linux-9
  • development
  • v1.0.4
  • v1.0.3
  • v1.0.2
7 results

TokenBuilder.php

Blame
  • TokenBuilder.php 5.87 KiB
    <?php
    
    namespace RAP;
    
    use \Firebase\JWT\JWT;
    
    class TokenBuilder {
    
        private $locator;
    
        public function __construct(Locator $locator) {
            $this->locator = $locator;
        }
    
        public function getIdToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string {
    
            $keyPair = $this->getNewestKeyPair();
    
            $payload = $this->createIdTokenPayloadArray($tokenData, $jwtCustomizer);
    
            return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
        }
    
        private function createIdTokenPayloadArray(AccessTokenData $tokenData, \Closure $jwtCustomizer = null) {
    
            $user = $this->locator->getUserDAO()->findUserById($tokenData->userId);
    
            $payloadArr = array(
                'iss' => $this->locator->config->jwtIssuer,
                'sub' => strval($user->id),
                'iat' => intval($tokenData->creationTime),
                'exp' => intval($tokenData->expirationTime),
                'aud' => $tokenData->clientId
            );
    
            $name = $user->getCompleteName();
            if ($name !== null) {
                $payloadArr['name'] = $name;
            }
    
            if (in_array("email", $tokenData->scope)) {
                $payloadArr['email'] = $user->getPrimaryEmail();
            }
            if (in_array("profile", $tokenData->scope)) {
                $payloadArr['given_name'] = $user->getName();
                $payloadArr['family_name'] = $user->getSurname();
                if ($user->getInstitution() !== null) {
                    $payloadArr['org'] = $user->getInstitution();
                }
            }
    
            if ($jwtCustomizer !== null) {
                // Add additional custom claims
                $jwtCustomizer($payloadArr);
            }
    
            return $payloadArr;
        }
    
        public function getAccessToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string {
    
            $keyPair = $this->getNewestKeyPair();
    
            $user = $this->locator->getUserDAO()->findUserById($tokenData->userId);
            if ($user === null) {
                // CLI client
                $sub = $tokenData->clientId;
            } else {
                $sub = $user->id;
            }
    
            $payload = array(
                'iss' => $this->locator->config->jwtIssuer,
                'sub' => strval($sub),
                'iat' => intval($tokenData->creationTime),
                'exp' => intval($tokenData->expirationTime),
                'aud' => $this->getAudience($tokenData),
                'scope' => implode(' ', $tokenData->scope)
            );
            if ($jwtCustomizer !== null) {
                // Add additional custom claims
                $jwtCustomizer($payload);
            }
    
            return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
        }
    
        private function getAudience(AccessTokenData $tokenData) {
    
            if ($tokenData->audience !== null) {
                return $this->getAudienceClaim($tokenData->audience);
            }
    
            $client = $this->locator->getBrowserBasedOAuth2ClientById($tokenData->clientId, true);
            if ($client === null) {
                // CLI client without audience
                return null;
            }
    
            $audiences = [$tokenData->clientId];
    
            if ($client->scopeAudienceMap !== null) {
                foreach ($tokenData->scope as $scope) {
                    if (array_key_exists($scope, $client->scopeAudienceMap)) {
                        $audience = ((array) $client->scopeAudienceMap)[$scope];
                        if (!in_array($audience, $audiences)) {
                            array_push($audiences, $audience);
                        }
                    }
                }
            }
    
            return $this->getAudienceClaim($audiences);
        }
    
        private function getAudienceClaim($audiences) {
            if (count($audiences) === 1) {
                // according to RFC 7519 audience can be a single value or an array
                return $audiences[0];
            }
            return $audiences;
        }
    
        public function generateToken(array $claims) {
    
            $iat = time();
    
            // basic payload
            $payload = array(
                'iss' => $this->locator->config->jwtIssuer,
                'iat' => $iat
            );
    
            // copy claims passed as parameter
            foreach ($claims as $key => $value) {
                $payload[$key] = $value;
            }
    
            // set expiration claim if it doesn't exist
            if (!array_key_exists('exp', $payload)) {
                $payload['exp'] = $iat + 3600;
            }
    
            $keyPair = $this->getNewestKeyPair();
            return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
        }
    
        /**
         * @param int $lifespan in hours
         * @param string $audience target service
         */
        public function generateNewToken(string $subject, int $lifespan, string $audience) {
            $keyPair = $this->getNewestKeyPair();
    
            $iat = time();
            $exp = $iat + $lifespan * 3600;
    
            $payload = array(
                'iss' => $this->locator->config->jwtIssuer,
                'sub' => strval($subject),
                'iat' => $iat,
                'exp' => $exp,
                'aud' => $audience
            );
    
            $conf = $this->getTokenIssuerConfig($audience);
            if (property_exists($conf, 'aud')) {
                $payload['aud'] = $conf->aud;
            }
            if (property_exists($conf, 'scope')) {
                $payload['scope'] = $conf->scope;
            }
    
            return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId);
        }
    
        private function getTokenIssuerConfig($audience) {
            foreach ($this->locator->config->tokenIssuer->services as $service) {
                if ($service->id === $audience) {
                    return $service;
                }
            }
            throw new \Exception("Unable to find configuration for " . $audience);
        }
    
        private function getNewestKeyPair(): RSAKeyPair {
    
            $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();
    
            if ($keyPair === null) {
                $keyPair = $this->locator->getJWKSHandler()->generateKeyPair();
            }
    
            return $keyPair;
        }
    
    }