You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
4.5 KiB
223 lines
4.5 KiB
<?php
|
|
|
|
namespace authkit2\Oidc;
|
|
use authkit2\Authkit2;
|
|
use authkit2\Oidc\Client;
|
|
use Firebase\JWT\JWT;
|
|
use Firebase\JWT\JWK;
|
|
|
|
/**
|
|
* A OpenId Connect token, optionally with a refresh token
|
|
*/
|
|
class Token
|
|
{
|
|
/**
|
|
* If we check for an expired token and it expires within this many seconds
|
|
* from now, just go ahead and refresh it early
|
|
* @var int
|
|
*/
|
|
const EXPIRATION_GRACE_PERIOD = 60;
|
|
|
|
/**
|
|
* Http client we've initialized with our authentication middleware for this
|
|
* token in place
|
|
* @var \GuzzleHttp\Client
|
|
*/
|
|
protected $client = null;
|
|
|
|
/**
|
|
* OIDC JWT access token
|
|
* @var string
|
|
*/
|
|
protected $access_token;
|
|
|
|
/**
|
|
* OIDC JWT refresh token
|
|
* @var string
|
|
*/
|
|
protected $refresh_token;
|
|
|
|
/**
|
|
* Cache of userinfo endpoint response
|
|
* @var array<string,scalar>
|
|
*/
|
|
protected $userinfo = null;
|
|
|
|
/**
|
|
* Cache of decoded JWT token
|
|
* @var array<string,mixed>
|
|
*/
|
|
protected $token_data = null;
|
|
|
|
/**
|
|
* Initialize token with the from*() static methods
|
|
*/
|
|
protected function __construct()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Create a token given a access_token and optionally refresh_token, passed
|
|
* as a string
|
|
*
|
|
* @param string $access_token
|
|
* @param ?string $refresh_token
|
|
* @return Token
|
|
*/
|
|
public static function fromString(string $access_token, ?string $refresh_token = null): Token
|
|
{
|
|
$token = new Token();
|
|
$token->access_token = $access_token;
|
|
$token->refresh_token = $refresh_token;
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Create a token from a OIDC response from the token endpoint
|
|
*
|
|
* @param object $response
|
|
* @return Token
|
|
*/
|
|
public static function fromResponse(object $response): Token
|
|
{
|
|
$token = new Token();
|
|
$token->access_token = $response->access_token ?? null;
|
|
$token->access_token_expires_at = isset($response->expires_in) ? time() + $response->expires_in : null;
|
|
$token->refresh_token = $response->refresh_token ?? null;
|
|
$token->refresh_token_expires_at = isset($response->refresh_expires_in) ? time() + $response->refresh_expires_in : null;
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Fetch an oidc client authenticated with this token
|
|
*
|
|
* @return Client
|
|
*/
|
|
public function getClient(): Client
|
|
{
|
|
if (!isset($this->client))
|
|
{
|
|
$this->client = new Client(new Authentication\TokenAuthentication($this));
|
|
}
|
|
return $this->client;
|
|
}
|
|
|
|
/**
|
|
* Refresh the access token
|
|
*
|
|
* @return void
|
|
*/
|
|
public function refresh()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Fetch the raw decoded data out of our JWT access token
|
|
*
|
|
* @return array<string,mixed>
|
|
*/
|
|
public function getTokenData(): array
|
|
{
|
|
if (!isset($this->token_data))
|
|
{
|
|
$this->token_data = json_decode(json_encode($this->decode()), true);
|
|
}
|
|
return $this->token_data;
|
|
}
|
|
|
|
/**
|
|
* Check whether the access token is expired
|
|
*
|
|
* If we fail to parse it because it's expired, or the expiration time is
|
|
* within EXPIRATION_GRACE_PERIOD seconds of now, we consider it expired.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isExpired(): bool
|
|
{
|
|
try
|
|
{
|
|
$data = $this->getTokenData();
|
|
}
|
|
catch (\Firebase\JWT\ExpiredException $ex)
|
|
{
|
|
return true;
|
|
}
|
|
if ($data['exp'] <= time() - static::EXPIRATION_GRACE_PERIOD)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Decode the access token as a JWT token
|
|
*
|
|
* @return object
|
|
*/
|
|
protected function decode(): object
|
|
{
|
|
$client = $this->getClient();
|
|
$jwks = Authkit2::cache('oidc.jwks', function() use ($client) {
|
|
$response = $client->get(Client::getOidcConfig()['jwks_uri']);
|
|
return json_decode(json_encode($response), true);
|
|
});
|
|
return JWT::decode($this->access_token, JWK::parseKeySet($jwks), Client::getOidcConfig()['id_token_signing_alg_values_supported']);
|
|
}
|
|
|
|
/**
|
|
* Fetch the underlying access token this token represents
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getAccessToken(): string
|
|
{
|
|
return $this->access_token;
|
|
}
|
|
|
|
/**
|
|
* Fetch the user's refresh token
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getRefreshToken(): string
|
|
{
|
|
return $this->refresh_token;
|
|
}
|
|
|
|
/**
|
|
* Fetch the user info associated with this token from the OIDC
|
|
* provider
|
|
*
|
|
* @return array<string,scalar>
|
|
*/
|
|
public function getUserInfo(): array
|
|
{
|
|
if (!isset($this->userinfo))
|
|
{
|
|
$this->userinfo = json_decode(json_encode($this->getClient()->get('userinfo')), true);
|
|
}
|
|
|
|
return $this->userinfo;
|
|
}
|
|
|
|
/**
|
|
* Fetch the roles encoded in this token
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function getRoles(): array
|
|
{
|
|
return $this->getTokenData()['realm_access']['roles'];
|
|
}
|
|
|
|
/**
|
|
* Fetch the uuid encoded in this token
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUserId(): string
|
|
{
|
|
return 'crn:user:'.$this->getTokenData()['sub'];
|
|
}
|
|
|
|
}
|
|
|