Commit 9be006f4 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added token exchange endpoint

parent e45fe334
Loading
Loading
Loading
Loading
+63 −0
Original line number Diff line number Diff line
@@ -77,4 +77,67 @@ class JWKSHandler {
        return $matches[1];
    }

    public function loadAllJWKS(): array {

        foreach ($this->locator->config->jwksUrls as $url) {
            $this->loadJWKS($url);
        }

        $dao = $this->locator->getJWKSDAO();
        return $dao->getAllPublicJWK();
    }

    private function loadJWKS($url) {

        $dao = $this->locator->getJWKSDAO();

        $conn = curl_init($url);
        curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);

        $result = curl_exec($conn);
        $info = curl_getinfo($conn);

        if ($info['http_code'] === 200) {
            $jwks = json_decode($result, TRUE);

            foreach ($jwks['keys'] as $key) {
                $key['url'] = $url;
                $jwk = $this->getPublicJWK($key);
                $dao->updatePublicJWK($jwk);
            }
        } else {
            $errorMessage = 'Error while retrieving JWKS: ' . curl_error($conn);
            error_log($result);
            curl_close($conn);
            http_response_code(500);
            die($errorMessage);
        }

        curl_close($conn);
    }

    private function getPublicJWK($data): PublicJWK {

        // Convert Base64 uri-safe variant to default (needed for JWKS)
        $n = strtr($data['n'], '-_', '+/');

        $rsa = new RSA();

        $key = "<RSAKeyPair>"
                . "<Modulus>" . $n . "</Modulus>"
                . "<Exponent>" . $data['e'] . "</Exponent>"
                . "</RSAKeyPair>";

        $rsa->loadKey($key, RSA::PUBLIC_FORMAT_XML);

        $jwk = new PublicJWK();
        $jwk->kid = $data['kid'];
        $jwk->key = $rsa;
        $jwk->url = $data['url'];
        $jwk->updateTime = time();

        return $jwk;
    }

}
+2 −4
Original line number Diff line number Diff line
@@ -101,17 +101,15 @@ class TokenBuilder {
     * @param int $lifespan in hours
     * @param string $audience target service
     */
    public function generateNewToken(int $lifespan, string $audience) {
    public function generateNewToken(string $subject, int $lifespan, string $audience) {
        $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair();

        $user = $this->locator->getSession()->getUser();

        $iat = time();
        $exp = $iat + $lifespan * 3600;

        $payload = array(
            'iss' => $this->locator->config->jwtIssuer,
            'sub' => strval($user->id),
            'sub' => strval($subject),
            'iat' => $iat,
            'exp' => $exp,
            'aud' => $audience
+86 −0
Original line number Diff line number Diff line
<?php

/* ----------------------------------------------------------------------------
 *               INAF - National Institute for Astrophysics
 *               IRA  - Radioastronomical Institute - Bologna
 *               OATS - Astronomical Observatory - Trieste
 * ----------------------------------------------------------------------------
 *
 * Copyright (C) 2016 Istituto Nazionale di Astrofisica
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License Version 3 as published by the
 * Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

namespace RAP;

use \Firebase\JWT\JWT;

class TokenExchanger {

    private $locator;

    public function __construct(Locator $locator) {
        $this->locator = $locator;
    }

    public function exchangeToken(string $token) {

        $key = $this->getKeyForToken($token);
        $decoded = JWT::decode($token, $key->key, ['RS256']);

        $subject = $decoded->sub;
        $lifespan = ($decoded->exp - time());

        $data = [];

        $data['access_token'] = $this->locator->getTokenBuilder()->generateNewToken($subject, $lifespan / 3600, "gms");
        $data['issued_token_type'] = "urn:ietf:params:oauth:token-type:access_token";
        $data['token_type'] = 'Bearer';
        $data['expires_in'] = $lifespan;

        return $data;
    }

    private function getKeyForToken(string $token): PublicJWK {

        $keys = $this->locator->getJWKSDAO()->getAllPublicJWK();

        $parts = explode('.', $token);
        $head = JWT::jsonDecode(JWT::urlsafeB64Decode($parts[0]));

        $kid = $head->kid;

        $key = $this->getKeyByKid($keys, $kid);
        if ($key === null) {
            $keys = $this->locator->getJWKSHandler()->loadAllJWKS();
        }
        $key = $this->getKeyByKid($keys, $kid);

        if ($key !== null) {
            return $key;
        }

        throw new \Exception("Invalid kid");
    }

    private function getKeyByKid(array $keys, string $kid): ?PublicJWK {
        foreach ($keys as $key) {
            if ($key->kid === $kid) {
                return $key;
            }
        }
        return null;
    }

}
+4 −0
Original line number Diff line number Diff line
@@ -11,4 +11,8 @@ interface JWKSDAO {
    public function insertRSAKeyPair(RSAKeyPair $keyPair): RSAKeyPair;

    public function getNewestKeyPair(): ?RSAKeyPair;

    public function getAllPublicJWK(): array;

    public function updatePublicJWK(PublicJWK $jwk);
}
+44 −0
Original line number Diff line number Diff line
@@ -85,4 +85,48 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO {
        return $keyPair;
    }

    public function getAllPublicJWK(): array {

        $dbh = $this->getDBHandler();

        $query = "SELECT `kid`, `key`, `url`, `update_time` FROM public_jwk";

        $stmt = $dbh->prepare($query);
        $stmt->execute();

        $keys = [];

        foreach ($stmt->fetchAll() as $row) {
            array_push($keys, $this->getPublicJWKFromResultRow($row));
        }

        return $keys;
    }

    private function getPublicJWKFromResultRow($row): PublicJWK {

        $jwk = new PublicJWK ();
        $jwk->key = $row['key'];
        $jwk->kid = $row['kid'];
        $jwk->url = $row['url'];
        $jwk->updateTime = $row['update_time'];
        return $jwk;
    }

    public function updatePublicJWK(PublicJWK $jwk) {

        $dbh = $this->getDBHandler();

        $query = "INSERT INTO public_jwk(kid, `key`, `url`, update_time) VALUES (:kid, :key, :url, :update_time)"
                . " ON DUPLICATE KEY UPDATE `key`=:key, `url`=:url, update_time=:update_time";

        $stmt = $dbh->prepare($query);
        $stmt->bindParam(':kid', $jwk->kid);
        $stmt->bindParam(':key', $jwk->key);
        $stmt->bindParam(':url', $jwk->url);
        $stmt->bindParam(':update_time', $jwk->updateTime);

        $stmt->execute();
    }

}
Loading