|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @copyright Bluz PHP Team |
|
4
|
|
|
* @link https://github.com/bluzphp/skeleton |
|
5
|
|
|
*/ |
|
6
|
|
|
|
|
7
|
|
|
/** |
|
8
|
|
|
* @namespace |
|
9
|
|
|
*/ |
|
10
|
|
|
namespace Application\Auth; |
|
11
|
|
|
|
|
12
|
|
|
use Application\Exception; |
|
13
|
|
|
use Application\Users; |
|
14
|
|
|
use Bluz\Application; |
|
15
|
|
|
use Bluz\Auth\AbstractTable; |
|
16
|
|
|
use Bluz\Auth\AuthException; |
|
17
|
|
|
use Bluz\Proxy\Auth; |
|
18
|
|
|
use Bluz\Proxy\Config; |
|
19
|
|
|
use Bluz\Proxy\Response; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* Auth Table |
|
23
|
|
|
* |
|
24
|
|
|
* @package Application\Auth |
|
25
|
|
|
* |
|
26
|
|
|
* @method static Row findRow($primaryKey) |
|
27
|
|
|
* @method static Row findRowWhere($whereList) |
|
28
|
|
|
* |
|
29
|
|
|
* @author Anton Shevchuk |
|
30
|
|
|
* @created 12.07.11 15:28 |
|
31
|
|
|
*/ |
|
32
|
|
|
class Table extends AbstractTable |
|
33
|
|
|
{ |
|
34
|
|
|
/** |
|
35
|
|
|
* Time that the token remains valid |
|
36
|
|
|
*/ |
|
37
|
|
|
const TOKEN_EXPIRATION_TIME = 1800; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Authenticate user by login/pass |
|
41
|
|
|
* |
|
42
|
|
|
* @param string $username |
|
43
|
|
|
* @param string $password |
|
44
|
|
|
* @throws AuthException |
|
45
|
|
|
* @throws Exception |
|
46
|
|
|
*/ |
|
47
|
2 |
|
public function authenticateEquals($username, $password) |
|
48
|
|
|
{ |
|
49
|
2 |
|
$authRow = $this->checkEquals($username, $password); |
|
50
|
|
|
|
|
51
|
|
|
// get user profile |
|
52
|
1 |
|
if (!$user = Users\Table::findRow($authRow->userId)) { |
|
53
|
|
|
throw new Exception("User is undefined in system"); |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
// try to login |
|
57
|
1 |
|
$user->tryLogin(); |
|
58
|
1 |
|
} |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* Check user by login/pass |
|
62
|
|
|
* |
|
63
|
|
|
* @param string $username |
|
64
|
|
|
* @param string $password |
|
65
|
|
|
* @throws AuthException |
|
66
|
|
|
* @return Row |
|
67
|
|
|
*/ |
|
68
|
4 |
|
public function checkEquals($username, $password) |
|
69
|
|
|
{ |
|
70
|
4 |
|
$authRow = $this->getAuthRow(self::PROVIDER_EQUALS, $username); |
|
71
|
|
|
|
|
72
|
4 |
|
if (!$authRow) { |
|
73
|
|
|
throw new AuthException("User not found"); |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
// verify password |
|
77
|
4 |
|
if (!$this->callVerifyFunction($password, $authRow->token)) { |
|
78
|
2 |
|
throw new AuthException("Wrong password"); |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
// get auth row |
|
82
|
2 |
|
return $authRow; |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* Authenticate user by login/pass |
|
87
|
|
|
* |
|
88
|
|
|
* @param Users\Row $user |
|
89
|
|
|
* @param string $password |
|
90
|
|
|
* @throws AuthException |
|
91
|
|
|
* @return Row |
|
92
|
|
|
*/ |
|
93
|
|
|
public function generateEquals($user, $password) |
|
94
|
|
|
{ |
|
95
|
|
|
// clear previous generated Auth record |
|
96
|
|
|
// works with change password |
|
97
|
|
|
$this->delete( |
|
98
|
|
|
[ |
|
99
|
|
|
'userId' => $user->id, |
|
100
|
|
|
'foreignKey' => $user->login, |
|
101
|
|
|
'provider' => self::PROVIDER_EQUALS, |
|
102
|
|
|
'tokenType' => self::TYPE_ACCESS |
|
103
|
|
|
] |
|
104
|
|
|
); |
|
105
|
|
|
|
|
106
|
|
|
// new auth row |
|
107
|
|
|
$row = new Row(); |
|
108
|
|
|
$row->userId = $user->id; |
|
109
|
|
|
$row->foreignKey = $user->login; |
|
110
|
|
|
$row->provider = self::PROVIDER_EQUALS; |
|
111
|
|
|
$row->tokenType = self::TYPE_ACCESS; |
|
112
|
|
|
|
|
113
|
|
|
// generate secret part is not required |
|
114
|
|
|
// encrypt password and save as token |
|
115
|
|
|
$row->token = $this->callHashFunction($password); |
|
116
|
|
|
|
|
117
|
|
|
$row->save(); |
|
118
|
|
|
|
|
119
|
|
|
return $row; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Generates cookie for authentication |
|
124
|
|
|
* |
|
125
|
|
|
* @throws \Bluz\Db\Exception\DbException |
|
126
|
|
|
*/ |
|
127
|
|
|
public function generateCookie() |
|
128
|
|
|
{ |
|
129
|
|
|
$hash = hash('md5', microtime(true)); |
|
130
|
|
|
$ttl = Config::getModuleData('users', 'rememberMe'); |
|
131
|
|
|
|
|
132
|
|
|
$this->delete( |
|
133
|
|
|
[ |
|
134
|
|
|
'userId' => app()->user()->id, |
|
135
|
|
|
'foreignKey' => app()->user()->login, |
|
136
|
|
|
'provider' => self::PROVIDER_COOKIE, |
|
137
|
|
|
'tokenType' => self::TYPE_ACCESS, |
|
138
|
|
|
] |
|
139
|
|
|
); |
|
140
|
|
|
|
|
141
|
|
|
$row = new Row(); |
|
142
|
|
|
$row->userId = app()->user()->id; |
|
143
|
|
|
$row->foreignKey = app()->user()->login; |
|
144
|
|
|
$row->provider = self::PROVIDER_COOKIE; |
|
145
|
|
|
$row->tokenType = self::TYPE_ACCESS; |
|
146
|
|
|
$row->expired = gmdate('Y-m-d H:i:s', time() + $ttl); |
|
147
|
|
|
|
|
148
|
|
|
$row->tokenSecret = $this->generateSecret(app()->user()->id); |
|
149
|
|
|
$row->token = hash('md5', $row->tokenSecret . $hash); |
|
150
|
|
|
|
|
151
|
|
|
$row->save(); |
|
152
|
|
|
|
|
153
|
|
|
Response::setCookie('rToken', $hash, time() + $ttl, '/'); |
|
154
|
|
|
Response::setCookie('rId', app()->user()->id, time() + $ttl, '/'); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Authenticate via cookie |
|
159
|
|
|
* |
|
160
|
|
|
* @param $userId |
|
161
|
|
|
* @param $token |
|
162
|
|
|
* @throws AuthException |
|
163
|
|
|
* @throws Exception |
|
164
|
|
|
*/ |
|
165
|
|
|
public function authenticateCookie($userId, $token) |
|
166
|
|
|
{ |
|
167
|
|
|
$authRow = $this->checkCookie($userId, $token); |
|
168
|
|
|
|
|
169
|
|
|
// get user row |
|
170
|
|
|
$user = Users\Table::findRow($authRow->userId); |
|
171
|
|
|
|
|
172
|
|
|
// try to login |
|
173
|
|
|
$user->tryLogin(); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* Check if supplied cookie is valid |
|
178
|
|
|
* |
|
179
|
|
|
* @param $userId |
|
180
|
|
|
* @param $token |
|
181
|
|
|
* @return Row |
|
182
|
|
|
* @throws AuthException |
|
183
|
|
|
*/ |
|
184
|
|
|
public function checkCookie($userId, $token) |
|
185
|
|
|
{ |
|
186
|
|
|
if (!$authRow = $this->findRowWhere(['userId' => $userId, 'provider' => self::PROVIDER_COOKIE])) { |
|
187
|
|
|
throw new AuthException('User not found'); |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
if (strtotime($authRow->expired) < time()) { |
|
191
|
|
|
$this->removeCookieToken($userId); |
|
192
|
|
|
throw new AuthException('Token has expired'); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
if ($authRow->token != hash('md5', $authRow->tokenSecret . $token)) { |
|
196
|
|
|
throw new AuthException('Incorrect token'); |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
return $authRow; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* Removes a cookie-token from database |
|
204
|
|
|
* |
|
205
|
|
|
* @param $userId |
|
206
|
|
|
* @throws \Bluz\Db\Exception\DbException |
|
207
|
|
|
*/ |
|
208
|
1 |
|
public function removeCookieToken($userId) |
|
209
|
|
|
{ |
|
210
|
1 |
|
$this->delete( |
|
211
|
|
|
[ |
|
212
|
1 |
|
'userId' => $userId, |
|
213
|
1 |
|
'provider' => self::PROVIDER_COOKIE |
|
214
|
|
|
] |
|
215
|
|
|
); |
|
216
|
1 |
|
} |
|
217
|
|
|
|
|
218
|
|
|
/** |
|
219
|
|
|
* Call Hash Function |
|
220
|
|
|
* |
|
221
|
|
|
* @param string $password |
|
222
|
|
|
* @throws \Application\Exception |
|
223
|
|
|
* @return string |
|
224
|
|
|
*/ |
|
225
|
1 |
View Code Duplication |
protected function callHashFunction($password) |
|
|
|
|
|
|
226
|
|
|
{ |
|
227
|
|
|
/** @var \Bluz\Auth\Auth $auth */ |
|
228
|
1 |
|
$auth = Auth::getInstance(); |
|
229
|
1 |
|
$options = $auth->getOption(self::PROVIDER_EQUALS); |
|
230
|
|
|
|
|
231
|
1 |
|
if (!isset($options['hash']) or |
|
|
|
|
|
|
232
|
1 |
|
!is_callable($options['hash']) |
|
233
|
|
|
) { |
|
234
|
|
|
throw new Exception("Hash function for 'equals' adapter is not callable"); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
// encrypt password with secret |
|
238
|
1 |
|
return call_user_func($options['hash'], $password); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
/** |
|
242
|
|
|
* Call Verify Function |
|
243
|
|
|
* |
|
244
|
|
|
* @param string $password |
|
245
|
|
|
* @param string $hash |
|
246
|
|
|
* @throws \Application\Exception |
|
247
|
|
|
* @return string |
|
248
|
|
|
*/ |
|
249
|
4 |
View Code Duplication |
protected function callVerifyFunction($password, $hash) |
|
|
|
|
|
|
250
|
|
|
{ |
|
251
|
|
|
/** @var \Bluz\Auth\Auth $auth */ |
|
252
|
4 |
|
$auth = Auth::getInstance(); |
|
253
|
4 |
|
$options = $auth->getOption(self::PROVIDER_EQUALS); |
|
254
|
|
|
|
|
255
|
4 |
|
if (!isset($options['verify']) or |
|
|
|
|
|
|
256
|
4 |
|
!is_callable($options['verify']) |
|
257
|
|
|
) { |
|
258
|
|
|
throw new Exception("Verify function for 'equals' adapter is not callable"); |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
// verify password with hash |
|
262
|
4 |
|
return call_user_func($options['verify'], $password, $hash); |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* authenticate user by token |
|
267
|
|
|
* |
|
268
|
|
|
* @param string $token |
|
269
|
|
|
* @throws \Bluz\Auth\AuthException |
|
270
|
|
|
* @return void |
|
271
|
|
|
*/ |
|
272
|
|
|
public function authenticateToken($token) |
|
273
|
|
|
{ |
|
274
|
|
|
$authRow = $this->checkToken($token); |
|
275
|
|
|
|
|
276
|
|
|
// get user profile |
|
277
|
|
|
$user = Users\Table::findRow($authRow->userId); |
|
278
|
|
|
|
|
279
|
|
|
// try to login |
|
280
|
|
|
$user->tryLogin(); |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* authenticate user by token |
|
285
|
|
|
* |
|
286
|
|
|
* @param string $token |
|
287
|
|
|
* @throws \Bluz\Auth\AuthException |
|
288
|
|
|
* @return Row |
|
289
|
|
|
*/ |
|
290
|
|
|
public function checkToken($token) |
|
291
|
|
|
{ |
|
292
|
|
|
if (!$authRow = $this->findRowWhere(['token' => $token, 'provider' => self::PROVIDER_TOKEN])) { |
|
293
|
|
|
throw new AuthException('Invalid token'); |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
if ($authRow->expired < gmdate('Y-m-d H:i:s')) { |
|
297
|
|
|
throw new AuthException('Token has expired'); |
|
298
|
|
|
} |
|
299
|
|
|
|
|
300
|
|
|
return $authRow; |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* @param $equalAuth |
|
305
|
|
|
* @return Row |
|
306
|
|
|
* @throws Exception |
|
307
|
|
|
* @throws \Bluz\Db\Exception\DbException |
|
308
|
|
|
*/ |
|
309
|
1 |
|
public function generateToken($equalAuth) |
|
310
|
|
|
{ |
|
311
|
|
|
// clear previous generated Auth record |
|
312
|
|
|
// works with change password |
|
313
|
1 |
|
$this->delete( |
|
314
|
|
|
[ |
|
315
|
1 |
|
'userId' => $equalAuth->userId, |
|
316
|
1 |
|
'foreignKey' => $equalAuth->foreignKey, |
|
317
|
1 |
|
'provider' => self::PROVIDER_TOKEN, |
|
318
|
1 |
|
'tokenType' => self::TYPE_ACCESS, |
|
319
|
|
|
] |
|
320
|
|
|
); |
|
321
|
|
|
// new auth row |
|
322
|
1 |
|
$row = new Row(); |
|
323
|
1 |
|
$row->userId = $equalAuth->userId; |
|
324
|
1 |
|
$row->foreignKey = $equalAuth->foreignKey; |
|
325
|
1 |
|
$row->provider = self::PROVIDER_TOKEN; |
|
326
|
1 |
|
$row->tokenType = self::TYPE_ACCESS; |
|
327
|
1 |
|
$row->expired = gmdate('Y-m-d H:i:s', time() + self::TOKEN_EXPIRATION_TIME); |
|
328
|
|
|
|
|
329
|
|
|
// generate secret |
|
330
|
1 |
|
$row->tokenSecret = $this->generateSecret($equalAuth->userId); |
|
331
|
|
|
|
|
332
|
|
|
// encrypt password and save as token |
|
333
|
1 |
|
$row->token = $this->callHashFunction($equalAuth->token); |
|
334
|
|
|
|
|
335
|
1 |
|
$row->save(); |
|
336
|
|
|
|
|
337
|
1 |
|
return $row; |
|
338
|
|
|
} |
|
339
|
|
|
} |
|
340
|
|
|
|
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.