1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
<?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)
{
global $conf;
$hash = hash('sha256', $user);
$file = $conf['metadir'] . '/jwt/' . $hash[0] . '/' . $hash . '.token';
io_makeFileDir($file);
return $file;
}
}
|