Merge pull request #4225 from dokuwiki/strcompatibility
[dokuwiki.git] / inc / JWT.php
blobce42a8520f44973ec0fdc4ece6d942c6eab10ad8
1 <?php
3 namespace dokuwiki;
5 /**
6 * Minimal JWT implementation
7 */
8 class JWT
10 protected $user;
11 protected $issued;
12 protected $secret;
14 /**
15 * Create a new JWT object
17 * Use validate() or create() to create a new instance
19 * @param string $user
20 * @param int $issued
22 protected function __construct($user, $issued)
24 $this->user = $user;
25 $this->issued = $issued;
28 /**
29 * Load the cookiesalt as secret
31 * @return string
33 protected static function getSecret()
35 return auth_cookiesalt(false, true);
38 /**
39 * Create a new instance from a token
41 * @param $token
42 * @return self
43 * @throws \Exception
45 public static function validate($token)
47 [$header, $payload, $signature] = sexplode('.', $token, 3, '');
48 $signature = base64_decode($signature);
50 if (!hash_equals($signature, hash_hmac('sha256', "$header.$payload", self::getSecret(), true))) {
51 throw new \Exception('Invalid JWT signature');
54 try {
55 $header = json_decode(base64_decode($header), true, 512, JSON_THROW_ON_ERROR);
56 $payload = json_decode(base64_decode($payload), true, 512, JSON_THROW_ON_ERROR);
57 } catch (\Exception $e) {
58 throw new \Exception('Invalid JWT', $e->getCode(), $e);
61 if (!$header || !$payload || !$signature) {
62 throw new \Exception('Invalid JWT');
65 if ($header['alg'] !== 'HS256') {
66 throw new \Exception('Unsupported JWT algorithm');
68 if ($header['typ'] !== 'JWT') {
69 throw new \Exception('Unsupported JWT type');
71 if ($payload['iss'] !== 'dokuwiki') {
72 throw new \Exception('Unsupported JWT issuer');
74 if (isset($payload['exp']) && $payload['exp'] < time()) {
75 throw new \Exception('JWT expired');
78 $user = $payload['sub'];
79 $file = self::getStorageFile($user);
80 if (!file_exists($file)) {
81 throw new \Exception('JWT not found, maybe it expired?');
84 if (file_get_contents($file) !== $token) {
85 throw new \Exception('JWT invalid, maybe it expired?');
88 return new self($user, $payload['iat']);
91 /**
92 * Create a new instance from a user
94 * Loads an existing token if available
96 * @param $user
97 * @return self
99 public static function fromUser($user)
101 $file = self::getStorageFile($user);
103 if (file_exists($file)) {
104 try {
105 return self::validate(io_readFile($file));
106 } catch (\Exception $ignored) {
110 $token = new self($user, time());
111 $token->save();
112 return $token;
117 * Get the JWT token for this instance
119 * @return string
121 public function getToken()
123 $header = [
124 'alg' => 'HS256',
125 'typ' => 'JWT',
127 $header = base64_encode(json_encode($header));
129 $payload = [
130 'iss' => 'dokuwiki',
131 'sub' => $this->user,
132 'iat' => $this->issued,
134 $payload = base64_encode(json_encode($payload, JSON_THROW_ON_ERROR));
136 $signature = hash_hmac('sha256', "$header.$payload", self::getSecret(), true);
137 $signature = base64_encode($signature);
138 return "$header.$payload.$signature";
142 * Save the token for the user
144 * Resets the issued timestamp
146 public function save()
148 $this->issued = time();
149 io_saveFile(self::getStorageFile($this->user), $this->getToken());
153 * Get the user of this token
155 * @return string
157 public function getUser()
159 return $this->user;
163 * Get the issued timestamp of this token
165 * @return int
167 public function getIssued()
169 return $this->issued;
173 * Get the storage file for this token
175 * Tokens are stored to be able to invalidate them
177 * @param string $user The user the token is for
178 * @return string
180 public static function getStorageFile($user)
182 return getCacheName($user, '.token');