1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
namespace Ntb\RestAPI; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Authentication mechanism using a token in the request header and validates it on every request. |
8
|
|
|
* |
9
|
|
|
* The mechanism works stateless. JWT is described in RFC 7519. |
10
|
|
|
* @author Christian Blank <[email protected]> |
11
|
|
|
*/ |
12
|
|
|
class JwtAuth extends \Object implements IAuth { |
13
|
|
|
|
14
|
|
|
public static function authenticate($email, $password) { |
15
|
|
|
$authenticator = \Injector::inst()->get('ApiMemberAuthenticator'); |
16
|
|
View Code Duplication |
if($user = $authenticator->authenticate(['Password' => $password, 'Email' => $email])) { |
|
|
|
|
17
|
|
|
return self::createSession($user); |
18
|
|
|
} |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @param Member $user |
23
|
|
|
* @return ApiSession |
24
|
|
|
*/ |
25
|
|
|
public static function createSession($user) { |
26
|
|
|
// create session |
27
|
|
|
$session = ApiSession::create(); |
28
|
|
|
$session->User = $user; |
|
|
|
|
29
|
|
|
$session->Token = JwtAuth::generate_token($user); |
30
|
|
|
return $session; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
public static function delete($request) { |
34
|
|
|
// nothing to do here |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @param \SS_HTTPRequest $request |
39
|
|
|
* @return \Member |
|
|
|
|
40
|
|
|
*/ |
41
|
|
View Code Duplication |
public static function current($request) { |
|
|
|
|
42
|
|
|
try { |
43
|
|
|
$token = AuthFactory::get_token($request); |
44
|
|
|
return self::get_member_from_token($token); |
45
|
|
|
} catch(\Exception $e) { |
46
|
|
|
\SS_Log::log($e->getMessage(), \SS_Log::INFO); |
47
|
|
|
} |
48
|
|
|
return false; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* |
53
|
|
|
* |
54
|
|
|
* @param string $token |
55
|
|
|
* @throws RestUserException |
56
|
|
|
* @return \Member |
|
|
|
|
57
|
|
|
*/ |
58
|
|
|
private static function get_member_from_token($token) { |
59
|
|
|
try { |
60
|
|
|
$data = self::jwt_decode($token, self::get_key()); |
61
|
|
|
if($data) { |
|
|
|
|
62
|
|
|
// todo: check expire time |
63
|
|
|
if(time() > $data['expire']) { |
64
|
|
|
throw new RestUserException("Session expired", 403); |
65
|
|
|
} |
66
|
|
|
$id = (int)$data['userId']; |
67
|
|
|
$user = \DataObject::get(\Config::inst()->get('BaseRestController', 'Owner'))->byID($id); |
68
|
|
|
if(!$user) { |
69
|
|
|
throw new RestUserException("Owner not found in database", 403); |
70
|
|
|
} |
71
|
|
|
return $user; |
72
|
|
|
} |
73
|
|
|
} catch(RestUserException $e) { |
74
|
|
|
throw $e; |
75
|
|
|
} catch(\Exception $e) { |
76
|
|
View Code Duplication |
if(\Director::isDev() && $token == \Config::inst()->get('JwtAuth', 'DevToken')) { |
|
|
|
|
77
|
|
|
return \DataObject::get(\Config::inst()->get('BaseRestController', 'Owner'))->first(); |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
throw new RestUserException("Token invalid", 403); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @param Member $user |
85
|
|
|
* @return string |
86
|
|
|
*/ |
87
|
|
|
private static function generate_token($user) { |
88
|
|
|
$iat = time(); |
89
|
|
|
$data = [ |
90
|
|
|
'iat' => $iat, |
91
|
|
|
'jti' => AuthFactory::generate_token($user), |
92
|
|
|
'iss' => \Config::inst()->get('JwtAuth', 'Issuer'), |
93
|
|
|
'expire' => $iat + \Config::inst()->get('JwtAuth', 'ExpireTime'), |
94
|
|
|
'userId' => $user->ID |
95
|
|
|
]; |
96
|
|
|
$key = self::get_key(); |
97
|
|
|
return self::jwt_encode($data, $key); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param array $data |
102
|
|
|
* @param string $key |
103
|
|
|
* @return string |
104
|
|
|
*/ |
105
|
|
|
public static function jwt_encode($data, $key) { |
106
|
|
|
$header = ['typ' => 'JWT']; |
107
|
|
|
$headerEncoded = self::base64_url_encode(json_encode($header)); |
108
|
|
|
$dataEncoded = self::base64_url_encode(json_encode($data)); |
109
|
|
|
$signature = hash_hmac(\Config::inst()->get('JwtAuth', 'HashAlgorithm'), "$headerEncoded.$dataEncoded", $key); |
110
|
|
|
return "$headerEncoded.$dataEncoded.$signature"; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
private static function get_key() { |
114
|
|
|
return \Config::inst()->get('JwtAuth', 'Key'); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @param string $token |
119
|
|
|
* @param string $key |
120
|
|
|
* @return array |
121
|
|
|
* @throws \Exception |
122
|
|
|
*/ |
123
|
|
|
public static function jwt_decode($token, $key) { |
124
|
|
|
$exploded = explode('.', $token); |
125
|
|
|
if(count($exploded) < 3) { |
126
|
|
|
throw new \Exception("No valid JWT token"); |
127
|
|
|
} |
128
|
|
|
list($headerEncoded, $dataEncoded, $signature) = $exploded; |
129
|
|
|
$selfRun = hash_hmac(\Config::inst()->get('JwtAuth', 'HashAlgorithm'), "$headerEncoded.$dataEncoded", $key); |
130
|
|
|
if($selfRun === $signature) { |
131
|
|
|
return json_decode(self::base64_url_decode($dataEncoded), true); |
132
|
|
|
} |
133
|
|
|
return false; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
static function base64_url_encode($data) { |
|
|
|
|
137
|
|
|
return rtrim(base64_encode($data), '='); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
static function base64_url_decode($base64) { |
|
|
|
|
141
|
|
|
return base64_decode(strtr($base64, '-_', '+/')); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.