1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* A basic authentication class. Include inside your APP or |
4
|
|
|
* on a page. You may have multiple Auth instances. Supports |
5
|
|
|
* 3rd party plugins. |
6
|
|
|
* |
7
|
|
|
* Use: |
8
|
|
|
* |
9
|
|
|
* $auth=$this->add('Auth'); |
10
|
|
|
* $auth->usePasswordEncryption(); |
11
|
|
|
* $auth->setModel('User'); |
12
|
|
|
* $auth->check(); |
13
|
|
|
* |
14
|
|
|
* Auth accessible from anywhere through $this->app->auth; |
15
|
|
|
* |
16
|
|
|
* Auth has several extensions, enable them like this: |
17
|
|
|
* |
18
|
|
|
* $auth->add('auth/Controller_DummyPopup'); // allows you to pick user from list and bypass password |
19
|
|
|
* $auth->add('auth/Controller_Cookie'); // adds "remember me" checkbox |
20
|
|
|
* |
21
|
|
|
* See documentation on "auth" add-on for more information |
22
|
|
|
* http://agiletoolkit.org/a/auth |
23
|
|
|
*/ |
24
|
|
|
class Auth_Basic extends AbstractController |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* Info will contain data loaded about authenticated user. |
28
|
|
|
* This property can be accessed through $this->get() and should not be changed after authentication. |
29
|
|
|
* |
30
|
|
|
* @var array|bool |
31
|
|
|
*/ |
32
|
|
|
public $info = false; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* This form is created when user is being asked about authentication. |
36
|
|
|
* If you are willing to change the way form looks, create it prior to calling check(). |
37
|
|
|
* Your form must have compatible field names: "username" and "password". |
38
|
|
|
* |
39
|
|
|
* @var Form |
40
|
|
|
*/ |
41
|
|
|
public $form = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string|callable Which encryption to use. Few are built-in |
45
|
|
|
*/ |
46
|
|
|
protected $password_encryption = null; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array Array of allowed page names |
50
|
|
|
*/ |
51
|
|
|
protected $allowed_pages = array(); |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string Login field name in model |
55
|
|
|
*/ |
56
|
|
|
public $login_field = 'email'; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var string Password field name in model |
60
|
|
|
*/ |
61
|
|
|
public $password_field = 'password'; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var int Encyption algorithm |
65
|
|
|
*/ |
66
|
|
|
public $hash_algo = PASSWORD_DEFAULT; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var array Encryption algorithm options |
70
|
|
|
*/ |
71
|
|
|
public $hash_options = array(); |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @var string Layout class |
75
|
|
|
*/ |
76
|
|
|
public $login_layout_class = 'Layout_Centered'; |
77
|
|
|
|
78
|
|
|
/** @var App_Frontend */ |
79
|
|
|
public $app; |
80
|
|
|
|
81
|
|
|
|
82
|
|
|
|
83
|
|
|
public function init() |
84
|
|
|
{ |
85
|
|
|
parent::init(); |
86
|
|
|
|
87
|
|
|
// Register as auth handler, if it's not set yet |
88
|
|
|
if (!isset($this->app->auth)) { |
89
|
|
|
$this->app->auth = $this; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
if (!$this->app->hasMethod('initializeSession') && !session_id()) { |
93
|
|
|
// No session support |
94
|
|
|
return; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
// Try to get information from the session. If user is authenticated, information will |
98
|
|
|
// be available there |
99
|
|
|
$this->info = (array) $this->recall('info', false); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Configure this Auth controller with a generic Model based on static |
104
|
|
|
* collection of user/password combinations. Use this method if you |
105
|
|
|
* only want one or few accounts to access the system. |
106
|
|
|
* |
107
|
|
|
* @param string|array $user Either string username or associative array with data |
108
|
|
|
* @param string $pass Password if username is string |
109
|
|
|
* |
110
|
|
|
* @return $this |
111
|
|
|
*/ |
112
|
|
|
public function allow($user, $pass = null) |
113
|
|
|
{ |
114
|
|
|
// creates fictional model to allow specified user and password |
115
|
|
|
// TODO: test this |
116
|
|
|
if ($this->model) { |
117
|
|
|
$this->model->table[] = array($this->login_field => $user, $this->password_field => $pass); |
118
|
|
|
|
119
|
|
|
return $this; |
120
|
|
|
} |
121
|
|
|
/** @type Model $m */ |
122
|
|
|
$m = $this->add('Model'); |
123
|
|
|
$m->setSource('Array', array( |
124
|
|
|
is_array($user) |
125
|
|
|
? $user |
126
|
|
|
: array($this->login_field => $user, $this->password_field => $pass) |
127
|
|
|
)); |
128
|
|
|
$m->id_field = $this->login_field; |
129
|
|
|
$this->setModel($m); |
130
|
|
|
|
131
|
|
|
return $this; |
132
|
|
|
} |
133
|
|
|
/** |
134
|
|
|
* Associate model with authentication class. Username / password |
135
|
|
|
* check will be performed against the model in the following steps: |
136
|
|
|
* Model will attempt to load record where login_field matches |
137
|
|
|
* specified. Password is then loaded and verified using configured |
138
|
|
|
* encryption method. |
139
|
|
|
* |
140
|
|
|
* @param string|object $model |
141
|
|
|
* @param string $login_field |
142
|
|
|
* @param string $password_field |
143
|
|
|
* |
144
|
|
|
* @return Model |
145
|
|
|
*/ |
146
|
|
|
public function setModel($model, $login_field = 'email', $password_field = 'password') |
147
|
|
|
{ |
148
|
|
|
parent::setModel($model); |
149
|
|
|
$this->login_field = $login_field; |
150
|
|
|
$this->password_field = $password_field; |
151
|
|
|
|
152
|
|
|
// Load model from session |
153
|
|
|
if ($this->info && $this->recall('id')) { |
154
|
|
|
if ($this->recall('class', false) == get_class($this->model)) { |
155
|
|
|
$this->debug('Loading model from cache'); |
156
|
|
|
$this->model->set($this->info); |
157
|
|
|
$this->model->dirty = array(); |
158
|
|
|
$this->model->id = $this->recall('id', null); |
159
|
|
|
} else { |
160
|
|
|
// Class changed, re-fetch data from database |
161
|
|
|
$this->debug('Class changed, loading from database'); |
162
|
|
|
$this->model->tryLoad($this->recall('id')); |
163
|
|
|
if (!$this->model->loaded()) { |
164
|
|
|
$this->logout(); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$this->memorizeModel(); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$id = $this->hook('tryLogin', array($model, $login_field, $password_field)); |
172
|
|
|
|
173
|
|
|
if ($id && is_numeric($id)) { |
174
|
|
|
$this->model->tryLoad($id); |
175
|
|
|
$this->memorizeModel(); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
$t = $this; |
179
|
|
|
|
180
|
|
|
// If model is saved, update our cache too, but don't store password |
181
|
|
|
$this->model->addHook('afterSave', function ($m) use ($t) { |
182
|
|
|
// after this model is saved, re-cache the info |
183
|
|
|
$tmp = $m->get(); |
184
|
|
|
unset($tmp[$t->password_field]); |
185
|
|
|
if ($t->app instanceof App_Web) { |
186
|
|
|
$t->memorize('info', $tmp); |
187
|
|
|
} |
188
|
|
|
}); |
189
|
|
|
|
190
|
|
|
$this->addEncryptionHook($this->model); |
191
|
|
|
|
192
|
|
|
if (strtolower($this->app->page) == 'logout') { |
193
|
|
|
$this->logout(); |
194
|
|
|
$this->app->redirect('/'); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return $this->model; |
198
|
|
|
} |
199
|
|
|
/** |
200
|
|
|
* Adds a hook to specified model which will encrypt password before save. |
201
|
|
|
* This method will be applied on $this->model, so you should not call |
202
|
|
|
* it manually. You can call it on a fresh model, however. |
203
|
|
|
* |
204
|
|
|
* @param Model $model |
205
|
|
|
*/ |
206
|
|
|
public function addEncryptionHook($model) |
207
|
|
|
{ |
208
|
|
|
// If model is saved, encrypt password |
209
|
|
|
$t = $this; |
210
|
|
|
if (isset($model->has_encryption_hook) && $model->has_encryption_hook) { |
|
|
|
|
211
|
|
|
return; |
212
|
|
|
} |
213
|
|
|
$model->has_encryption_hook = true; |
214
|
|
|
$model->addHook('beforeSave', function ($m) use ($t) { |
215
|
|
|
if ($m->isDirty($t->password_field) && $m[$t->password_field]) { |
216
|
|
|
$m[$t->password_field] = $t->encryptPassword($m[$t->password_field], $m[$t->login_field]); |
217
|
|
|
} |
218
|
|
|
}); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Destroy object |
223
|
|
|
*/ |
224
|
|
|
public function destroy() |
225
|
|
|
{ |
226
|
|
|
unset($this->app->auth); |
227
|
|
|
parent::destroy(); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Auth memorizes data about a logged-in user in session. You can either use this function to access |
232
|
|
|
* that data or $auth->model (preferred) $auth->get('username') will always point to the login field |
233
|
|
|
* value ofthe user regardless of how your field is named. |
234
|
|
|
* |
235
|
|
|
* @param string $property |
236
|
|
|
* @param mixed $default |
237
|
|
|
* |
238
|
|
|
* @return mixed |
239
|
|
|
*/ |
240
|
|
|
public function get($property = null, $default = null) |
241
|
|
|
{ |
242
|
|
|
if (is_null($property)) { |
243
|
|
|
return $this->info; |
244
|
|
|
} |
245
|
|
|
if (!isset($this->info[$property])) { |
246
|
|
|
return $default; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return $this->info[$property]; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Return array of all authenticated session info |
254
|
|
|
* |
255
|
|
|
* @return array |
256
|
|
|
*/ |
257
|
|
|
public function getAll() |
258
|
|
|
{ |
259
|
|
|
return $this->info; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Specify page or array of pages which will exclude authentication. Add your registration page here |
264
|
|
|
* or page containing terms and conditions. |
265
|
|
|
* |
266
|
|
|
* @param string|array $page |
267
|
|
|
* |
268
|
|
|
* @return $this |
269
|
|
|
*/ |
270
|
|
|
public function allowPage($page) |
271
|
|
|
{ |
272
|
|
|
if (is_array($page)) { |
273
|
|
|
foreach ($page as $p) { |
274
|
|
|
$this->allowPage($p); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return $this; |
278
|
|
|
} |
279
|
|
|
$this->allowed_pages[] = $page; |
280
|
|
|
|
281
|
|
|
return $this; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Return array of all allowed page names |
286
|
|
|
* |
287
|
|
|
* @return array |
288
|
|
|
*/ |
289
|
|
|
public function getAllowedPages() |
290
|
|
|
{ |
291
|
|
|
return $this->allowed_pages; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Verifies if the specified page is allowed to be accessed without |
296
|
|
|
* authentication. |
297
|
|
|
* |
298
|
|
|
* @param string $page |
299
|
|
|
* |
300
|
|
|
* @return bool |
301
|
|
|
*/ |
302
|
|
|
public function isPageAllowed($page) |
303
|
|
|
{ |
304
|
|
|
if ($this->hook('isPageAllowed', array($page)) === true) { |
305
|
|
|
return true; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
return in_array($page, $this->allowed_pages) || in_array(str_replace('_', '/', $page), $this->allowed_pages); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Specifies how password will be encrypted when stored. It's recommended |
313
|
|
|
* that you do not specify encryption method, in which case a built-in |
314
|
|
|
* password_hash() will be used, which is defined by PHP. |
315
|
|
|
* |
316
|
|
|
* Some other values are "sha256/salt", "md5", "rot13". Note that if your |
317
|
|
|
* application is already using 'md5' or 'sha1', you can remove the |
318
|
|
|
* argument entirely and your user passwords will keep working and will |
319
|
|
|
* automatically be "upgraded" to password_hash when used. |
320
|
|
|
* |
321
|
|
|
* If you are having trouble with authentication, use auth->debug() |
322
|
|
|
* |
323
|
|
|
* @param string|callable $method |
324
|
|
|
* |
325
|
|
|
* @return $this |
326
|
|
|
*/ |
327
|
|
|
public function usePasswordEncryption($method = 'php') |
328
|
|
|
{ |
329
|
|
|
$this->password_encryption = $method; |
330
|
|
|
|
331
|
|
|
return $this; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Manually encrypt password |
336
|
|
|
* |
337
|
|
|
* @param string $password |
338
|
|
|
* @param string $salt |
339
|
|
|
* |
340
|
|
|
* @return string|bool Returns false on failure, encrypted string otherwise |
341
|
|
|
*/ |
342
|
|
|
public function encryptPassword($password, $salt = null) |
343
|
|
|
{ |
344
|
|
|
if (!is_string($this->password_encryption) && is_callable($this->password_encryption)) { |
345
|
|
|
$e = $this->password_encryption; |
346
|
|
|
|
347
|
|
|
return $e($password, $salt); |
348
|
|
|
} |
349
|
|
|
if ($this->password_encryption) { |
350
|
|
|
$this->debug("Encrypting password: '$password' with ".$this->password_encryption.' salt='.$salt); |
351
|
|
|
} |
352
|
|
|
switch ($this->password_encryption) { |
353
|
|
|
case null: |
354
|
|
|
return $password; |
355
|
|
|
case 'php': |
356
|
|
|
// returns false on failure |
357
|
|
|
return password_hash($password, $this->hash_algo, $this->hash_options); |
358
|
|
|
case 'sha256/salt': |
359
|
|
|
if ($salt === null) { |
360
|
|
|
throw $this->exception( |
361
|
|
|
'sha256 requires salt (2nd argument to encryptPassword and is normaly an email)' |
362
|
|
|
); |
363
|
|
|
} |
364
|
|
|
$key = $this->app->getConfig('auth/key', $this->app->name); |
365
|
|
|
if ($this->password_encryption) { |
366
|
|
|
$this->debug('Using key '.$key); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
return hash_hmac('sha256', $password.$salt, $key); |
370
|
|
|
case 'sha1': |
371
|
|
|
return sha1($password); |
372
|
|
|
case 'md5': |
373
|
|
|
return md5($password); |
374
|
|
|
case 'rot13': |
375
|
|
|
return str_rot13($password); |
376
|
|
|
default: |
377
|
|
|
throw $this->exception('No such encryption method') |
378
|
|
|
->addMoreInfo('encryption', $this->password_encryption); |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Call this function to perform a check for logged in user. This will also display a login-form |
384
|
|
|
* and will verify user's credential. If you want to handle log-in form on your own, use |
385
|
|
|
* auth->isLoggedIn() to check and redirect user to a login page. |
386
|
|
|
* |
387
|
|
|
* check() returns true if user have just logged in and will return "null" for requests when user |
388
|
|
|
* continues to use his session. Use that to perform some calculation on log-in |
389
|
|
|
* |
390
|
|
|
* @return bool |
391
|
|
|
*/ |
392
|
|
|
public function check() |
393
|
|
|
{ |
394
|
|
|
if ($this->isPageAllowed($this->app->page)) { |
395
|
|
|
return; |
396
|
|
|
} // no authentication is required |
397
|
|
|
|
398
|
|
|
// Check if user's session contains autentication information |
399
|
|
|
if (!$this->isLoggedIn()) { |
400
|
|
|
$this->memorizeURL(); |
401
|
|
|
|
402
|
|
|
// Brings up additional verification methods, such as cookie |
403
|
|
|
// authentication, token or OpenID. In case of successful login, |
404
|
|
|
// breakHook($user_id) must be used |
405
|
|
|
$user_id = $this->hook('check'); |
406
|
|
|
if (!is_array($user_id) && !is_bool($user_id) && $user_id) { |
407
|
|
|
$this->model->load($user_id); |
408
|
|
|
//$this->loggedIn(); |
409
|
|
|
$this->memorizeModel(); |
410
|
|
|
|
411
|
|
|
return true; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/* |
415
|
|
|
$this->debug('User is not authenticated yet'); |
416
|
|
|
|
417
|
|
|
// No information is present. Let's see if cookie is set |
418
|
|
|
} |
419
|
|
|
}else $this->debug("No permanent cookie"); |
420
|
|
|
*/ |
421
|
|
|
$this->processLogin(); |
422
|
|
|
|
423
|
|
|
return true; |
424
|
|
|
} else { |
425
|
|
|
$this->debug('User is already authenticated'); |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Add additional info to be stored in user session. |
431
|
|
|
* |
432
|
|
|
* @param string|array $key |
433
|
|
|
* @param mixed $val |
434
|
|
|
* |
435
|
|
|
* @return $this |
436
|
|
|
*/ |
437
|
|
|
public function addInfo($key, $val = null) |
438
|
|
|
{ |
439
|
|
|
if (is_array($key)) { |
440
|
|
|
foreach ($key as $a => $b) { |
441
|
|
|
$this->addInfo($a, $b); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
return $this; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
$this->debug("Gathered info: $key=$val"); |
448
|
|
|
$this->info[$key] = $val; |
449
|
|
|
|
450
|
|
|
return $this; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* This function determines - if user is already logged in or not. It does it by |
455
|
|
|
* looking at $this->info, which was loaded during init() from session. |
456
|
|
|
* |
457
|
|
|
* @return bool |
458
|
|
|
*/ |
459
|
|
|
public function isLoggedIn() |
460
|
|
|
{ |
461
|
|
|
return $this->model->loaded(); |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
/** |
465
|
|
|
* This function verifies credibility of supplied authenication data. |
466
|
|
|
* It will search based on user and verify the password. It's also |
467
|
|
|
* possible that the function will re-hash user password with |
468
|
|
|
* updated hash. |
469
|
|
|
* |
470
|
|
|
* if default authentication method is used, the function will |
471
|
|
|
* automatically determine hash used for password generation and will |
472
|
|
|
* upgrade to a new php5.5-compatible syntax. |
473
|
|
|
* |
474
|
|
|
* This function return false OR the id of the record matching user. |
475
|
|
|
* |
476
|
|
|
* @param string $user |
477
|
|
|
* @param string $password |
478
|
|
|
* |
479
|
|
|
* @return mixed |
480
|
|
|
*/ |
481
|
|
|
public function verifyCredentials($user, $password) |
482
|
|
|
{ |
483
|
|
|
$this->debug('verifying credentials for '.$user.' '.$password); |
484
|
|
|
|
485
|
|
|
// First, perhaps model has a method for verifying credentials. |
486
|
|
|
if ($this->model->hasMethod('verifyCredentials')) { |
487
|
|
|
return $this->model->verifyCredentials($user, $password); |
|
|
|
|
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
// If password field is not defined in the model for security |
491
|
|
|
// reasons, let's add it for authentication purpose. |
492
|
|
|
$password_existed = true; |
493
|
|
|
if (!$this->model->hasElement($this->password_field)) { |
494
|
|
|
$this->model->addField($this->password_field)->type('password'); |
495
|
|
|
$password_existed = false; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
// Attempt to load user data by username. If not found, return false |
499
|
|
|
/** @type Model $data User model */ |
500
|
|
|
$data = $this->model->newInstance(); |
501
|
|
|
|
502
|
|
|
$data->tryLoadBy($this->login_field, $user); |
503
|
|
View Code Duplication |
if (!$data->loaded()) { |
504
|
|
|
$this->debug('user with login '.$user.' could not be loaded'); |
505
|
|
|
if (!$password_existed) { |
506
|
|
|
$data->getElement($this->password_field)->destroy(); |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
return false; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
$hash = $data[$this->password_field]; |
513
|
|
|
|
514
|
|
|
$this->debug('loaded user entry with hash: '.$hash); |
515
|
|
|
|
516
|
|
|
// Verify password first |
517
|
|
|
$result = false; |
518
|
|
|
$rehash = false; |
519
|
|
|
|
520
|
|
|
if ($this->password_encryption == 'php') { |
521
|
|
|
|
522
|
|
|
// Get information about the hash |
523
|
|
|
$info = password_get_info($hash); |
524
|
|
|
|
525
|
|
|
// Backwards-compatibility with older ATK encryption methods |
526
|
|
|
if ($info['algo'] == 0) { |
527
|
|
|
|
528
|
|
|
// Determine type of hash by length |
529
|
|
|
if (strlen($hash) == 64) { |
530
|
|
|
$this->password_encryption = 'sha256/salt'; |
531
|
|
|
} elseif (strlen($hash) == 32) { |
532
|
|
|
$this->password_encryption = 'md5'; |
533
|
|
|
} elseif (strlen($hash) == 40) { |
534
|
|
|
$this->password_encryption = 'sha1'; |
535
|
|
|
} else { |
536
|
|
|
$this->password_encryption = null; |
537
|
|
|
$this->debug('Unable to identify password hash type, using plain-text matching'); |
538
|
|
|
/* |
539
|
|
|
$this->password_encryption='php'; |
540
|
|
|
$data->unload(); |
541
|
|
|
if (!$password_existed) { |
542
|
|
|
$data->getElement($this->password_field)->destroy(); |
543
|
|
|
} |
544
|
|
|
return false; |
545
|
|
|
*/ |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
// Convert password hash |
549
|
|
|
$this->debug('Old password found with algo='.$this->password_encryption); |
550
|
|
|
$ep = $this->encryptPassword($password, $user); |
551
|
|
|
$this->password_encryption = 'php'; |
552
|
|
|
|
553
|
|
|
$rehash = true; |
554
|
|
|
$result = $hash == $ep; |
555
|
|
|
} else { |
556
|
|
|
$result = password_verify($password, $ep = $data[$this->password_field]); |
557
|
|
|
$this->debug('Native password hash with info: '.json_encode($info)); |
558
|
|
|
$rehash = password_needs_rehash( |
559
|
|
|
$hash, |
560
|
|
|
$this->hash_algo, |
561
|
|
|
$this->hash_options |
562
|
|
|
); |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
if ($result) { |
566
|
|
|
$this->debug('Verify is a SUCCESS'); |
567
|
|
|
|
568
|
|
|
if ($rehash) { |
569
|
|
|
$hash = $data[$this->password_field] = $password; |
570
|
|
|
$data->setDirty($this->password_field); |
571
|
|
|
$data->save(); |
572
|
|
|
$this->debug('Rehashed into '.$data[$this->password_field]); |
573
|
|
|
} |
574
|
|
|
} |
575
|
|
|
} else { |
576
|
|
|
$ep = $this->encryptPassword($password, $user); |
577
|
|
|
$result = $hash == $ep; |
578
|
|
|
$this->debug('Attempting algo='.$this->password_encryption.' hash='.$hash.' newhash='.$ep); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
View Code Duplication |
if (!$result) { |
582
|
|
|
$this->debug('Verify is a FAIL'); |
583
|
|
|
$data->unload(); |
584
|
|
|
if (!$password_existed) { |
585
|
|
|
$data->getElement($this->password_field)->destroy(); |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
return false; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
// Leave record loaded, but hide password |
592
|
|
|
$data[$this->password_field] = ''; |
593
|
|
|
$data->dirty[$this->password_field] = false; |
594
|
|
|
|
595
|
|
|
return $data[$this->model->id_field]; |
596
|
|
|
|
597
|
|
|
/* |
598
|
|
|
if (!$password_existed) { |
599
|
|
|
$data->getElement($this->password_field)->destroy(); |
600
|
|
|
} |
601
|
|
|
*/ |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
/** |
605
|
|
|
* Memorize current URL. Called when the first unsuccessful check is executed. |
606
|
|
|
*/ |
607
|
|
|
public function memorizeURL() |
608
|
|
|
{ |
609
|
|
|
if ($this->app->page !== 'index' && !$this->recall('page', false)) { |
610
|
|
|
$this->memorize('page', $this->app->page); |
611
|
|
|
$g = $_GET; |
612
|
|
|
unset($g['page']); |
613
|
|
|
$this->memorize('args', $g); |
614
|
|
|
} |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
/** |
618
|
|
|
* Return originalally requested URL. |
619
|
|
|
* |
620
|
|
|
* @return string |
621
|
|
|
*/ |
622
|
|
|
public function getURL() |
623
|
|
|
{ |
624
|
|
|
$p = $this->recall('page'); |
625
|
|
|
|
626
|
|
|
// If there is a login page, no need to return to it |
627
|
|
|
if ($p == 'login') { |
628
|
|
|
return $this->app->url('/'); |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
$url = $this->app->url($p, $this->recall('args', null)); |
632
|
|
|
$this->forget('page'); |
633
|
|
|
$this->forget('args'); |
634
|
|
|
|
635
|
|
|
return $url; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
/** |
639
|
|
|
* Rederect to page user tried to access before authentication was requested. |
640
|
|
|
*/ |
641
|
|
|
public function loginRedirect() |
642
|
|
|
{ |
643
|
|
|
$this->debug('to Index'); |
644
|
|
|
$this->app->redirect($this->getURL()); |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* This function is always executed after successfull login through a normal means (login form or plugin). |
649
|
|
|
* |
650
|
|
|
* It will create cache model data. |
651
|
|
|
* |
652
|
|
|
* @param string $user |
653
|
|
|
* @param string $pass |
654
|
|
|
*/ |
655
|
|
|
public function loggedIn($user = null, $pass = null) |
656
|
|
|
{ |
657
|
|
|
//$username,$password,$memorize=false){ |
658
|
|
|
$this->hook('loggedIn', array($user, $pass)); |
659
|
|
|
$this->app->redirect($this->getURL()); |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Store model in session data so that it can be retrieved faster. |
664
|
|
|
*/ |
665
|
|
|
public function memorizeModel() |
666
|
|
|
{ |
667
|
|
|
if (!$this->model->loaded()) { |
668
|
|
|
throw $this->exception('Authentication failure', 'AccessDenied'); |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
// Don't store password in model / memory / session |
672
|
|
|
$this->model['password'] = null; |
673
|
|
|
unset($this->model->dirty['password']); |
674
|
|
|
|
675
|
|
|
// Cache memory. Should we use controller? |
676
|
|
|
$this->info = $this->model->get(); |
677
|
|
|
$this->info['username'] = $this->info[$this->login_field]; |
678
|
|
|
|
679
|
|
|
if ($this->app->hasMethod('initializeSession') || session_id()) { |
680
|
|
|
$this->memorize('info', $this->info); |
681
|
|
|
$this->memorize('class', get_class($this->model)); |
682
|
|
|
$this->memorize('id', $this->model->id); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
$this->hook('login'); |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
/** |
689
|
|
|
* Manually Log in as specified users. Will not perform password check or redirect. |
690
|
|
|
* |
691
|
|
|
* @param mixed $id |
692
|
|
|
* |
693
|
|
|
* @return $this |
694
|
|
|
*/ |
695
|
|
|
public function loginByID($id) |
696
|
|
|
{ |
697
|
|
|
$this->model->load($id); |
698
|
|
|
$this->memorizeModel(); |
699
|
|
|
|
700
|
|
|
return $this; |
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
/** |
704
|
|
|
* Manually Log in with specified condition. |
705
|
|
|
* |
706
|
|
|
* @param string $field |
707
|
|
|
* @param mixed $value |
708
|
|
|
* |
709
|
|
|
* @return $this |
710
|
|
|
*/ |
711
|
|
|
public function loginBy($field, $value) |
712
|
|
|
{ |
713
|
|
|
$this->model->tryLoadBy($field, $value); |
714
|
|
|
$this->memorizeModel(); |
715
|
|
|
|
716
|
|
|
return $this; |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
/** |
720
|
|
|
* Manually Log in as specified users by using login name. |
721
|
|
|
* |
722
|
|
|
* @param string $user |
723
|
|
|
* |
724
|
|
|
* @return $this |
725
|
|
|
*/ |
726
|
|
|
public function login($user) |
727
|
|
|
{ |
728
|
|
|
if (is_object($user)) { |
729
|
|
|
if (!$this->model) { |
730
|
|
|
throw $this->exception('Auth Model should be set'); |
731
|
|
|
} |
732
|
|
|
$c = get_class($this->model); |
733
|
|
|
|
734
|
|
|
if (!$user instanceof $c) { |
735
|
|
|
throw $this->exception('Specified model with incompatible class') |
736
|
|
|
->addMoreInfo('required', $c) |
737
|
|
|
->addMoreInfo('supplied', get_class($user)); |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
$this->model = $user; |
741
|
|
|
$this->memorizeModel(); |
742
|
|
|
|
743
|
|
|
return $this; |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
$this->model->tryLoadBy($this->login_field, $user); |
747
|
|
|
$this->memorizeModel(); |
748
|
|
|
|
749
|
|
|
return $this; |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
/** |
753
|
|
|
* Manually log out user. |
754
|
|
|
* |
755
|
|
|
* @return $this |
756
|
|
|
*/ |
757
|
|
|
public function logout() |
758
|
|
|
{ |
759
|
|
|
$this->hook('logout'); |
760
|
|
|
|
761
|
|
|
$this->model->unload(); |
762
|
|
|
|
763
|
|
|
// maybe can use $this->app->destroySession() here instead? |
764
|
|
|
$this->forget('info'); |
765
|
|
|
$this->forget('id'); |
766
|
|
|
|
767
|
|
|
setcookie(session_name(), '', time() - 42000, '/'); |
768
|
|
|
if (session_status() === PHP_SESSION_ACTIVE) { |
769
|
|
|
session_destroy(); |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
$this->info = false; |
773
|
|
|
|
774
|
|
|
return $this; |
775
|
|
|
} |
776
|
|
|
|
777
|
|
|
/** |
778
|
|
|
* Creates log-in form. |
779
|
|
|
* Override if you want to use your own form. If you need to change template used by a log-in form, |
780
|
|
|
* add template/default/page/login.html. |
781
|
|
|
* |
782
|
|
|
* @param Page $page |
783
|
|
|
* |
784
|
|
|
* @return Form |
785
|
|
|
*/ |
786
|
|
|
public function createForm($page) |
787
|
|
|
{ |
788
|
|
|
/** @type Form $form */ |
789
|
|
|
$form = $page->add('Form', null, null, array('form/minimal')); |
790
|
|
|
|
791
|
|
|
/** @type Field $email */ |
792
|
|
|
$email = $this->model->hasField($this->login_field); |
793
|
|
|
$email = $email ? $email->caption() : 'E-mail'; |
794
|
|
|
|
795
|
|
|
/** @type Field $password */ |
796
|
|
|
$password = $this->model->hasField($this->password_field); |
797
|
|
|
$password = $password ? $password->caption() : 'Password'; |
798
|
|
|
|
799
|
|
|
$form->addField('Line', 'username', $email); |
800
|
|
|
$form->addField('Password', 'password', $password); |
801
|
|
|
$form->addSubmit('Login')->addClass('atk-jackscrew')->addClass('atk-swatch-green'); |
802
|
|
|
|
803
|
|
|
//$form->add('View',null,'button_row_left')->addClass('atk-jackscrew'); |
804
|
|
|
|
805
|
|
|
return $form; |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
/** |
809
|
|
|
* Do not override this function. |
810
|
|
|
* |
811
|
|
|
* @return Page |
812
|
|
|
*/ |
813
|
|
|
public function showLoginForm() |
814
|
|
|
{ |
815
|
|
|
$this->app->template->trySet('page_title', 'Login'); |
816
|
|
|
if ($this->app->layout && $this->login_layout_class) { |
817
|
|
|
$this->app->layout->destroy(); |
818
|
|
|
$this->app->add($this->login_layout_class); |
819
|
|
|
/** @type Page $p */ |
820
|
|
|
$p = $this->app->layout->add('Page', null, null, array('page/login')); |
821
|
|
|
} else { |
822
|
|
|
/** @type Page $p */ |
823
|
|
|
$p = $this->app->add('Page', null, null, array('page/login')); |
824
|
|
|
} |
825
|
|
|
$this->app->page_object = $p; |
826
|
|
|
|
827
|
|
|
// hook: createForm use this to build basic login form |
828
|
|
|
$this->form = $this->hook('createForm', array($p)); |
829
|
|
|
|
830
|
|
|
// If no hook, build standard form |
831
|
|
|
if (!is_object($this->form)) { |
832
|
|
|
$this->form = $this->createForm($p); |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
$this->hook('updateForm'); |
836
|
|
|
$f = $this->form; |
837
|
|
|
if ($f->isSubmitted()) { |
838
|
|
|
$id = $this->verifyCredentials($f->get('username'), $f->get('password')); |
839
|
|
|
if ($id) { |
840
|
|
|
$this->loginByID($id); |
841
|
|
|
$this->loggedIn($f->get('username'), $f->get('password')); |
842
|
|
|
exit; |
843
|
|
|
} |
844
|
|
|
$f->getElement('password')->displayFieldError('Incorrect login information'); |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
return $p; |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
/** |
851
|
|
|
* Do not override this function. |
852
|
|
|
*/ |
853
|
|
|
public function processLogin() |
854
|
|
|
{ |
855
|
|
|
$this->memorizeURL(); |
856
|
|
|
$this->app->template->tryDel('Menu'); |
857
|
|
|
$this->showLoginForm(); |
858
|
|
|
|
859
|
|
|
$this->app->hook('post-init'); |
860
|
|
|
$this->app->hook('pre-exec'); |
861
|
|
|
|
862
|
|
|
if (isset($_GET['submit']) && $_POST) { |
863
|
|
|
$this->app->hook('submitted'); |
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
$this->app->hook('post-submit'); |
867
|
|
|
$this->app->execute(); |
868
|
|
|
exit; |
869
|
|
|
} |
870
|
|
|
} |
871
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.