185 lines
4.3 KiB
PHP
185 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace dokuwiki;
|
|
|
|
/**
|
|
* Minimal JWT implementation
|
|
*/
|
|
class JWT
|
|
{
|
|
protected $user;
|
|
protected $issued;
|
|
protected $secret;
|
|
|
|
/**
|
|
* Create a new JWT object
|
|
*
|
|
* Use validate() or create() to create a new instance
|
|
*
|
|
* @param string $user
|
|
* @param int $issued
|
|
*/
|
|
protected function __construct($user, $issued)
|
|
{
|
|
$this->user = $user;
|
|
$this->issued = $issued;
|
|
}
|
|
|
|
/**
|
|
* Load the cookiesalt as secret
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function getSecret()
|
|
{
|
|
return auth_cookiesalt(false, true);
|
|
}
|
|
|
|
/**
|
|
* Create a new instance from a token
|
|
*
|
|
* @param $token
|
|
* @return self
|
|
* @throws \Exception
|
|
*/
|
|
public static function validate($token)
|
|
{
|
|
[$header, $payload, $signature] = sexplode('.', $token, 3, '');
|
|
$signature = base64_decode($signature);
|
|
|
|
if (!hash_equals($signature, hash_hmac('sha256', "$header.$payload", self::getSecret(), true))) {
|
|
throw new \Exception('Invalid JWT signature');
|
|
}
|
|
|
|
try {
|
|
$header = json_decode(base64_decode($header), true, 512, JSON_THROW_ON_ERROR);
|
|
$payload = json_decode(base64_decode($payload), true, 512, JSON_THROW_ON_ERROR);
|
|
} catch (\Exception $e) {
|
|
throw new \Exception('Invalid JWT', $e->getCode(), $e);
|
|
}
|
|
|
|
if (!$header || !$payload || !$signature) {
|
|
throw new \Exception('Invalid JWT');
|
|
}
|
|
|
|
if ($header['alg'] !== 'HS256') {
|
|
throw new \Exception('Unsupported JWT algorithm');
|
|
}
|
|
if ($header['typ'] !== 'JWT') {
|
|
throw new \Exception('Unsupported JWT type');
|
|
}
|
|
if ($payload['iss'] !== 'dokuwiki') {
|
|
throw new \Exception('Unsupported JWT issuer');
|
|
}
|
|
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
|
throw new \Exception('JWT expired');
|
|
}
|
|
|
|
$user = $payload['sub'];
|
|
$file = self::getStorageFile($user);
|
|
if (!file_exists($file)) {
|
|
throw new \Exception('JWT not found, maybe it expired?');
|
|
}
|
|
|
|
if (file_get_contents($file) !== $token) {
|
|
throw new \Exception('JWT invalid, maybe it expired?');
|
|
}
|
|
|
|
return new self($user, $payload['iat']);
|
|
}
|
|
|
|
/**
|
|
* Create a new instance from a user
|
|
*
|
|
* Loads an existing token if available
|
|
*
|
|
* @param $user
|
|
* @return self
|
|
*/
|
|
public static function fromUser($user)
|
|
{
|
|
$file = self::getStorageFile($user);
|
|
|
|
if (file_exists($file)) {
|
|
try {
|
|
return self::validate(io_readFile($file));
|
|
} catch (\Exception $ignored) {
|
|
}
|
|
}
|
|
|
|
$token = new self($user, time());
|
|
$token->save();
|
|
return $token;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the JWT token for this instance
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getToken()
|
|
{
|
|
$header = [
|
|
'alg' => 'HS256',
|
|
'typ' => 'JWT',
|
|
];
|
|
$header = base64_encode(json_encode($header));
|
|
|
|
$payload = [
|
|
'iss' => 'dokuwiki',
|
|
'sub' => $this->user,
|
|
'iat' => $this->issued,
|
|
];
|
|
$payload = base64_encode(json_encode($payload, JSON_THROW_ON_ERROR));
|
|
|
|
$signature = hash_hmac('sha256', "$header.$payload", self::getSecret(), true);
|
|
$signature = base64_encode($signature);
|
|
return "$header.$payload.$signature";
|
|
}
|
|
|
|
/**
|
|
* Save the token for the user
|
|
*
|
|
* Resets the issued timestamp
|
|
*/
|
|
public function save()
|
|
{
|
|
$this->issued = time();
|
|
io_saveFile(self::getStorageFile($this->user), $this->getToken());
|
|
}
|
|
|
|
/**
|
|
* Get the user of this token
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getUser()
|
|
{
|
|
return $this->user;
|
|
}
|
|
|
|
/**
|
|
* Get the issued timestamp of this token
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getIssued()
|
|
{
|
|
return $this->issued;
|
|
}
|
|
|
|
/**
|
|
* Get the storage file for this token
|
|
*
|
|
* Tokens are stored to be able to invalidate them
|
|
*
|
|
* @param string $user The user the token is for
|
|
* @return string
|
|
*/
|
|
public static function getStorageFile($user)
|
|
{
|
|
return getCacheName($user, '.token');
|
|
}
|
|
}
|