*/ 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 */ 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 */ 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 */ protected function getLaravelCallbacks(): array { return [ 'session_get' => /** * Fetch a variable from the session. * @param string $key * @return mixed */ static function(string $key) { return \Session::get($key); }, 'session_set' => /** * Set a variable in the session. * @param string $key * @param mixed $value * @return void */ static function(string $key, $value): void { \Session::put($key, $value); }, 'cache_get' => /** * Fetch a value from cache. * @param string $key * @return mixed */ static function(string $key) { return \Cache::get($key); }, 'cache_set' => /** * Set a value in cache. * @param string $key * @param mixed $value * @return void */ static 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(); } elseif (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); } }