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.
332 lines
8.2 KiB
332 lines
8.2 KiB
<?php
|
|
|
|
namespace authkit2;
|
|
|
|
/**
|
|
* Helper class to abstract away the framework authkit2 is running on as well
|
|
* as provide simple helper methods for using it outside of a framework.
|
|
*
|
|
* @method static mixed cache(string $key, callable $generator)
|
|
* @method static mixed cache_get(string $key, mixed $default = null)
|
|
* @method static void cache_set(string $key, mixed $value)
|
|
* @method static mixed session_get(string $key)
|
|
* @method static void session_set(string $key, mixed $value)
|
|
* @method static void configure(string $client_id, string $client_secret, string $endpoint)
|
|
* @method static Oidc\Client get_client()
|
|
* @method static Oidc\Token get_token(string $access_token, ?string $refresh_token = null)
|
|
* @method static Oidc\Token refresh_token(Oidc\Token $token)
|
|
*/
|
|
class Authkit2
|
|
{
|
|
/**
|
|
* All data we shove into the session/cache will have its key prefixed
|
|
* with this value.
|
|
* @var string
|
|
*/
|
|
private const LIB_PREFIX = 'authkit2.';
|
|
|
|
/**
|
|
* Functions this class provides
|
|
* @array<string,callable>
|
|
*/
|
|
protected $callbacks = [];
|
|
|
|
/**
|
|
* Oidc client with the application credentials
|
|
* @var \authkit2\Oidc\Client
|
|
*/
|
|
protected $client;
|
|
|
|
/**
|
|
* Try and detect if we recognize the environment the library is running
|
|
* in and adjust our implementations accordingly.
|
|
*
|
|
* Basically, if we see the LARAVEL_START constant we assume Laravel and
|
|
* use Laravel facades, otherwise we use native php implementations.
|
|
*
|
|
*/
|
|
protected function __construct()
|
|
{
|
|
$callbacks = $this->getCommonCallbacks();
|
|
if (defined('LARAVEL_START'))
|
|
{
|
|
$callbacks = array_merge($callbacks, $this->getLaravelCallbacks());
|
|
}
|
|
else
|
|
{
|
|
$callbacks = array_merge($callbacks, $this->getNativeCallbacks());
|
|
}
|
|
$this->callbacks = $callbacks;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the instance of Authkit2 class
|
|
*
|
|
* @return Authkit2
|
|
*/
|
|
public static function get(): Authkit2
|
|
{
|
|
static $authkit2;
|
|
if (!isset($authkit2))
|
|
{
|
|
$authkit2 = new Authkit2();
|
|
}
|
|
return $authkit2;
|
|
}
|
|
|
|
/**
|
|
* Override any of the function implementations
|
|
*
|
|
* Name is the same as the callable function name, e.g.,
|
|
* Authkit2::cache_set() can be overriden with Authkit2->cache_set = function(...) {}
|
|
*
|
|
* @param string $name
|
|
* @param callable $value
|
|
* @return void
|
|
*/
|
|
public function __set(string $name, $value): void
|
|
{
|
|
if (!array_key_exists($name, $this->callbacks))
|
|
{
|
|
trigger_error('Undefined property: '.__CLASS__.'::$'.$name, E_USER_WARNING);
|
|
return;
|
|
}
|
|
|
|
if (!is_callable($value))
|
|
{
|
|
throw new \Exception('Authkit2::'.$name.' value must be callable');
|
|
}
|
|
|
|
$this->callbacks[$name] = $value;
|
|
}
|
|
|
|
/**
|
|
* Call any of the provided methods
|
|
*
|
|
* @param string $name
|
|
* @param mixed[] $arguments
|
|
* @return mixed
|
|
*/
|
|
public static function __callStatic(string $name, array $arguments)
|
|
{
|
|
$authkit2 = static::get();
|
|
if (!isset($authkit2->callbacks[$name]))
|
|
{
|
|
trigger_error('Call to undefined method '.__CLASS__.'::'.$name.'()', E_USER_ERROR);
|
|
}
|
|
|
|
return call_user_func_array($authkit2->callbacks[$name], $arguments);
|
|
}
|
|
|
|
/**
|
|
* Helper method for getting cache values, and generating and setting if
|
|
* they do not exist.
|
|
*
|
|
* @param string $key cache key
|
|
* @param callable $generator method that returns the value if we do not have it cached
|
|
* @return mixed
|
|
*/
|
|
protected function cache_helper(string $key, callable $generator)
|
|
{
|
|
$value = static::cache_get($key, null);
|
|
if (!isset($value))
|
|
{
|
|
$value = $generator();
|
|
static::cache_set($key, $value);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Initialize common library functions that don't require an environment-specific
|
|
* implementation
|
|
*
|
|
* @return array<string,callable>
|
|
*/
|
|
protected function getCommonCallbacks(): array
|
|
{
|
|
return [
|
|
'cache' => [$this, 'cache_helper'],
|
|
'configure' => [$this, 'ak2_configure'],
|
|
'get_client' => [$this, 'ak2_get_client'],
|
|
'get_token' => [$this, 'ak2_get_token'],
|
|
'refresh_token' => [$this, 'ak2_refresh_token']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Initialize the class by binding all the PHP native implementations of
|
|
* functions
|
|
*
|
|
* @return array<string,callable>
|
|
*/
|
|
protected function getNativeCallbacks(): array
|
|
{
|
|
return [
|
|
'session_get' => [$this, 'native_session_get'],
|
|
'session_set' => [$this, 'native_session_set'],
|
|
'cache_get' => [$this, 'native_cache_get'],
|
|
'cache_set' => [$this, 'native_cache_set']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Initialize the class by binding Laravel adapters as the implementation
|
|
* of all functions
|
|
*
|
|
* @return array<string,callable>
|
|
*/
|
|
protected function getLaravelCallbacks(): array
|
|
{
|
|
return [
|
|
'session_get' =>
|
|
/**
|
|
* Fetch a variable from the session
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
function(string $key) { return \Session::get($key); },
|
|
'session_set' =>
|
|
/**
|
|
* Set a variable in the session
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
function(string $key, $value): void { \Session::put($key, $value); },
|
|
'cache_get' =>
|
|
/**
|
|
* Fetch a value from cache
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
function(string $key) { return \Cache::get($key); },
|
|
'cache_set' =>
|
|
/**
|
|
* Set a value in cache
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
function(string $key, $value): void { \Cache::set($key, $value); }
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Retrieve a property out of the $_SESSION variable; null if the
|
|
* property doesn't exist.
|
|
*
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
protected function native_session_get(string $key)
|
|
{
|
|
$this->native_session_check();
|
|
return $_SESSION[static::LIB_PREFIX.$key] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Set a value in the $_SESSION variable
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
protected function native_session_set(string $key, $value): void
|
|
{
|
|
$this->native_session_check();
|
|
$_SESSION[static::LIB_PREFIX.$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* Check whether a PHP session exists, and if not try and start one
|
|
*
|
|
* @internal
|
|
* @return void
|
|
*/
|
|
protected function native_session_check(): void
|
|
{
|
|
if (session_status() == \PHP_SESSION_NONE)
|
|
session_start();
|
|
else if (session_status() == \PHP_SESSION_DISABLED)
|
|
throw new \Exception("Authkit2 requires PHP sessions are enabled");
|
|
}
|
|
|
|
/**
|
|
* Dummy cache implementation to avoid errors; always returns default
|
|
*
|
|
* @todo Check if apcu is available and use if so? Fall back to temp files?
|
|
* @param string $key cache key to retrieve
|
|
* @param mixed $default value to return if the specified key is not found
|
|
* @return mixed
|
|
*/
|
|
protected function native_cache_get(string $key, $default = null)
|
|
{
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* Dummy cache implementation
|
|
*
|
|
* @param string $key cache key to set
|
|
* @param mixed $value value to cache
|
|
* @return void
|
|
*/
|
|
protected function native_cache_set(string $key, $value): void
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Configure the authkit2 library
|
|
*
|
|
* @param string $client_id
|
|
* @param string $client_secret
|
|
* @param string $endpoint
|
|
* @return void
|
|
*/
|
|
protected function ak2_configure(string $client_id, string $client_secret, string $endpoint): void
|
|
{
|
|
$this->client = new \authkit2\Oidc\Client($endpoint, $client_id, $client_secret);
|
|
}
|
|
|
|
/**
|
|
* Fetch a OIDC client authenticated as this application
|
|
*
|
|
* @return Oidc\Client
|
|
*/
|
|
protected function ak2_get_client(): Oidc\Client
|
|
{
|
|
return $this->client;
|
|
}
|
|
|
|
/**
|
|
* Given the essential values from a token (access token, refresh token),
|
|
* convert that into a Token object that can be used to make and authenticate
|
|
* requests.
|
|
*
|
|
* The refresh token is not strictly required, however if the token is
|
|
* expired then requests will simply fail. This use case is intended for
|
|
* authenticating requests using tokens other applications have sent to us.
|
|
*
|
|
* @param string $access_token
|
|
* @param ?string $refresh_token
|
|
* @return Oidc\Token
|
|
*/
|
|
protected function ak2_get_token(string $access_token, ?string $refresh_token = null): Oidc\Token
|
|
{
|
|
return Oidc\Token::fromString($this->client, $access_token, $refresh_token);
|
|
}
|
|
|
|
/**
|
|
* Refresh a token object -- generate a new access token from its
|
|
* refresh_token.
|
|
*
|
|
* @param Oidc\Token $token
|
|
* @return Oidc\Token a newly generated token
|
|
*/
|
|
protected function ak2_refresh_token(Oidc\Token $token): Oidc\Token
|
|
{
|
|
return $this->client->refreshToken($token);
|
|
}
|
|
|
|
}
|
|
|