|
1
|
|
|
<?php |
|
2
|
|
|
namespace Auth; |
|
3
|
|
|
|
|
4
|
|
|
if(!function_exists('password_hash') || !function_exists('password_verify')) |
|
5
|
|
|
{ |
|
6
|
|
|
define('PASSWORD_BCRYPT', 1); |
|
7
|
|
|
define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); |
|
8
|
|
|
define('PASSWORD_BCRYPT_DEFAULT_COST', 10); |
|
9
|
|
|
|
|
10
|
|
|
function password_hash($password, $algo = PASSWORD_DEFAULT) |
|
11
|
|
|
{ |
|
12
|
|
|
if(is_null($password) || is_int($password)) |
|
13
|
|
|
{ |
|
14
|
|
|
$password = (string)$password; |
|
15
|
|
|
} |
|
16
|
|
|
if(!is_string($password)) |
|
17
|
|
|
{ |
|
18
|
|
|
trigger_error("password_hash(): Password must be a string", E_USER_WARNING); |
|
19
|
|
|
return false; |
|
20
|
|
|
} |
|
21
|
|
|
if(!is_int($algo)) |
|
22
|
|
|
{ |
|
23
|
|
|
trigger_error("password_hash() expects parameter 2 to be long, ".gettype($algo)." given", E_USER_WARNING); |
|
24
|
|
|
return false; |
|
25
|
|
|
} |
|
26
|
|
|
switch($algo) |
|
27
|
|
|
{ |
|
28
|
|
|
case PASSWORD_BCRYPT: |
|
29
|
|
|
$cost = PASSWORD_BCRYPT_DEFAULT_COST; |
|
30
|
|
|
$rawSaltLen = 16; |
|
31
|
|
|
$requiredSaltLen = 22; |
|
32
|
|
|
$hashFormat = sprintf("$2y$%02d$", $cost); |
|
33
|
|
|
$resultLength = 60; |
|
34
|
|
|
break; |
|
35
|
|
|
default: |
|
36
|
|
|
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); |
|
37
|
|
|
return false; |
|
38
|
|
|
} |
|
39
|
|
|
$salt = openssl_random_pseudo_bytes($rawSaltLen); |
|
40
|
|
|
$base64Digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
|
41
|
|
|
$bcrypt64Digits = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
|
42
|
|
|
$base64String = base64_encode($salt); |
|
43
|
|
|
$salt = strtr(rtrim($base64String, '='), $base64Digits, $bcrypt64Digits); |
|
44
|
|
|
$salt = substr($salt, 0, $requiredSaltLen); |
|
45
|
|
|
$hash = $hashFormat.$salt; |
|
46
|
|
|
$ret = crypt($password, $hash); |
|
47
|
|
|
if(!is_string($ret) || strlen($ret) != $resultLength) |
|
48
|
|
|
{ |
|
49
|
|
|
return false; |
|
50
|
|
|
} |
|
51
|
|
|
return $ret; |
|
52
|
|
|
} |
|
53
|
|
|
|
|
54
|
|
|
function password_verify($password, $hash) |
|
55
|
|
|
{ |
|
56
|
|
|
$ret = crypt($password, $hash); |
|
57
|
|
|
if(!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) |
|
58
|
|
|
{ |
|
59
|
|
|
return false; |
|
60
|
|
|
} |
|
61
|
|
|
$status = 0; |
|
62
|
|
|
$count = strlen($ret); |
|
63
|
|
|
for($i = 0; $i < $count; $i++) |
|
64
|
|
|
{ |
|
65
|
|
|
$status |= (ord($ret[$i]) ^ ord($hash[$i])); |
|
66
|
|
|
} |
|
67
|
|
|
return $status === 0; |
|
68
|
|
|
} |
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
|
class SQLAuthenticator extends Authenticator |
|
72
|
|
|
{ |
|
73
|
|
|
public $dataSet = null; |
|
74
|
|
|
public $pendingDataSet = null; |
|
75
|
|
|
private $dataTables = array(); |
|
76
|
|
|
private $params; |
|
77
|
|
|
|
|
78
|
|
|
public function __construct($params) |
|
79
|
|
|
{ |
|
80
|
|
|
parent::__construct($params); |
|
81
|
|
|
$this->params = $params; |
|
82
|
|
|
if($this->current) |
|
83
|
|
|
{ |
|
84
|
|
|
$this->dataSet = $this->getCurrentDataSet(); |
|
85
|
|
|
} |
|
86
|
|
|
if($this->pending) |
|
87
|
|
|
{ |
|
88
|
|
|
$this->pendingDataSet = $this->getPendingDataSet(); |
|
89
|
|
|
} |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* @SuppressWarnings("StaticAccess") |
|
94
|
|
|
*/ |
|
95
|
|
|
private function getCurrentDataSet() |
|
96
|
|
|
{ |
|
97
|
|
|
if(isset($this->params['current_data_set'])) |
|
98
|
|
|
{ |
|
99
|
|
|
return \DataSetFactory::getDataSetByName($this->params['current_data_set']); |
|
100
|
|
|
} |
|
101
|
|
|
return \DataSetFactory::getDataSetByName('authentication'); |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* @SuppressWarnings("StaticAccess") |
|
106
|
|
|
*/ |
|
107
|
|
|
private function getPendingDataSet() |
|
108
|
|
|
{ |
|
109
|
|
|
if(isset($this->params['pending_data_set'])) |
|
110
|
|
|
{ |
|
111
|
|
|
return \DataSetFactory::getDataSetByName($this->params['pending_data_set']); |
|
112
|
|
|
} |
|
113
|
|
|
return \DataSetFactory::getDataSetByName('pending_authentication'); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
private function getDataTable($name, $pending = false) |
|
117
|
|
|
{ |
|
118
|
|
|
if(isset($this->dataTables[$name]) && isset($this->dataTables[$name][$pending])) |
|
119
|
|
|
{ |
|
120
|
|
|
return $this->dataTables[$name][$pending]; |
|
121
|
|
|
} |
|
122
|
|
|
$dataSet = $this->dataSet; |
|
123
|
|
|
if($pending) |
|
124
|
|
|
{ |
|
125
|
|
|
$dataSet = $this->pendingDataSet; |
|
126
|
|
|
} |
|
127
|
|
|
if($dataSet === null) |
|
128
|
|
|
{ |
|
129
|
|
|
throw new \Exception('Unable to obtain dataset for SQL Authentication!'); |
|
130
|
|
|
} |
|
131
|
|
|
$dataTable = $dataSet[$name]; |
|
132
|
|
|
if(!isset($this->dataTables[$name])) |
|
133
|
|
|
{ |
|
134
|
|
|
$this->dataTables[$name] = array(); |
|
135
|
|
|
} |
|
136
|
|
|
$this->dataTables[$name][$pending] = $dataTable; |
|
137
|
|
|
return $dataTable; |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* Get the data table for Pending Users |
|
142
|
|
|
* |
|
143
|
|
|
* @return boolean|\Data\DataTable The Pending User Data Table |
|
144
|
|
|
*/ |
|
145
|
|
|
private function getPendingUserDataTable() |
|
146
|
|
|
{ |
|
147
|
|
|
if(isset($this->params['pending_user_table'])) |
|
148
|
|
|
{ |
|
149
|
|
|
return $this->getDataTable($this->params['pending_user_table'], true); |
|
150
|
|
|
} |
|
151
|
|
|
return $this->getDataTable('users', true); |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
public function login($username, $password) |
|
155
|
|
|
{ |
|
156
|
|
|
if($this->current === false) |
|
157
|
|
|
{ |
|
158
|
|
|
return false; |
|
159
|
|
|
} |
|
160
|
|
|
$userDataTable = $this->getDataTable('user'); |
|
161
|
|
|
$filter = new \Data\Filter("uid eq '$username'"); |
|
162
|
|
|
$users = $userDataTable->read($filter); |
|
163
|
|
|
if($users === false || !isset($users[0])) |
|
164
|
|
|
{ |
|
165
|
|
|
return false; |
|
166
|
|
|
} |
|
167
|
|
|
if(password_verify($password, $users[0]['pass'])) |
|
168
|
|
|
{ |
|
169
|
|
|
return array('res'=>true, 'extended'=>$users[0]); |
|
|
|
|
|
|
170
|
|
|
} |
|
171
|
|
|
return false; |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
public function isLoggedIn($data) |
|
175
|
|
|
{ |
|
176
|
|
|
if(isset($data['res'])) |
|
177
|
|
|
{ |
|
178
|
|
|
return $data['res']; |
|
179
|
|
|
} |
|
180
|
|
|
return false; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
public function getUser($data) |
|
184
|
|
|
{ |
|
185
|
|
|
if(isset($this->params['current_data_set'])) |
|
186
|
|
|
{ |
|
187
|
|
|
$data['current_data_set'] = $this->params['current_data_set']; |
|
188
|
|
|
} |
|
189
|
|
|
return new SQLUser($data, $this); |
|
|
|
|
|
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
View Code Duplication |
public function getGroupByName($name) |
|
|
|
|
|
|
193
|
|
|
{ |
|
194
|
|
|
$groupDataTable = $this->getDataTable('group'); |
|
195
|
|
|
$filter = new \Data\Filter("gid eq '$name'"); |
|
196
|
|
|
$groups = $groupDataTable->read($filter); |
|
197
|
|
|
if($groups === false || !isset($groups[0])) |
|
198
|
|
|
{ |
|
199
|
|
|
return false; |
|
|
|
|
|
|
200
|
|
|
} |
|
201
|
|
|
return new SQLGroup($groups[0]); |
|
|
|
|
|
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
View Code Duplication |
public function getUserByName($name) |
|
|
|
|
|
|
205
|
|
|
{ |
|
206
|
|
|
$userDataTable = $this->getDataTable('user'); |
|
207
|
|
|
$filter = new \Data\Filter("uid eq '$name'"); |
|
208
|
|
|
$users = $userDataTable->read($filter); |
|
209
|
|
|
if($users === false || !isset($users[0])) |
|
210
|
|
|
{ |
|
211
|
|
|
return false; |
|
|
|
|
|
|
212
|
|
|
} |
|
213
|
|
|
return new SQLUser($users[0], $this); |
|
|
|
|
|
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
private function getDataByFilter($dataTableName, $filter, $select, $top, $skip, $orderby) |
|
217
|
|
|
{ |
|
218
|
|
|
$dataTable = $this->getDataTable($dataTableName); |
|
219
|
|
|
return $dataTable->read($filter, $select, $top, $skip, $orderby); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
/** |
|
223
|
|
|
* @param string $dataTableName The Data Table to serach |
|
224
|
|
|
* @param string $className The class to obtain data in |
|
225
|
|
|
* @param boolean|array $select The fields to read |
|
226
|
|
|
* @param boolean|integer $top The number of entities to read |
|
227
|
|
|
* @param boolean|integer $skip The number of entities to skip |
|
228
|
|
|
* @param boolean|array $orderby The fields to sort by |
|
229
|
|
|
*/ |
|
230
|
|
|
private function convertDataToClass($dataTableName, $className, $filter, $select, $top, $skip, $orderby) |
|
231
|
|
|
{ |
|
232
|
|
|
$data = $this->getDataByFilter($dataTableName, $filter, $select, $top, $skip, $orderby); |
|
233
|
|
|
if($data === false) |
|
234
|
|
|
{ |
|
235
|
|
|
return false; |
|
236
|
|
|
} |
|
237
|
|
|
$count = count($data); |
|
238
|
|
|
for($i = 0; $i < $count; $i++) |
|
239
|
|
|
{ |
|
240
|
|
|
$data[$i] = new $className($groups[$i], $this); |
|
|
|
|
|
|
241
|
|
|
} |
|
242
|
|
|
return $data; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* @param boolean|array $select The fields to read |
|
247
|
|
|
* @param boolean|integer $top The number of entities to read |
|
248
|
|
|
* @param boolean|integer $skip The number of entities to skip |
|
249
|
|
|
* @param boolean|array $orderby The fields to sort by |
|
250
|
|
|
*/ |
|
251
|
|
|
public function getGroupsByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false) |
|
252
|
|
|
{ |
|
253
|
|
|
return $this->convertDataToClass('group', 'SQLGroup', $filter, $select, $top, $skip, $orderby); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* @param boolean|array $select The fields to read |
|
258
|
|
|
* @param boolean|integer $top The number of entities to read |
|
259
|
|
|
* @param boolean|integer $skip The number of entities to skip |
|
260
|
|
|
* @param boolean|array $orderby The fields to sort by |
|
261
|
|
|
*/ |
|
262
|
|
|
public function getUsersByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false) |
|
263
|
|
|
{ |
|
264
|
|
|
return $this->convertDataToClass('group', 'SQLUser', $filter, $select, $top, $skip, $orderby); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
public function getPendingUserCount() |
|
268
|
|
|
{ |
|
269
|
|
|
if($this->pending === false) |
|
270
|
|
|
{ |
|
271
|
|
|
return 0; |
|
272
|
|
|
} |
|
273
|
|
|
$dataTable = $this->getPendingUserDataTable(); |
|
274
|
|
|
if($dataTable === null) |
|
275
|
|
|
{ |
|
276
|
|
|
return 0; |
|
277
|
|
|
} |
|
278
|
|
|
return $dataTable->count(); |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
private function searchPendingUsers($filter, $select, $top, $skip, $orderby) |
|
282
|
|
|
{ |
|
283
|
|
|
$userDataTable = $this->getPendingUserDataTable(); |
|
284
|
|
|
$fieldData = $filter->to_mongo_filter(); |
|
285
|
|
|
$firstFilter = new \Data\Filter('substringof(data,"'.implode($fieldData, ' ').'")'); |
|
286
|
|
|
$users = $userDataTable->read($firstFilter, $select, $top, $skip, $orderby); |
|
|
|
|
|
|
287
|
|
|
if($users === false) |
|
288
|
|
|
{ |
|
289
|
|
|
return false; |
|
290
|
|
|
} |
|
291
|
|
|
$ret = array(); |
|
292
|
|
|
$count = count($users); |
|
293
|
|
|
for($i = 0; $i < $count; $i++) |
|
294
|
|
|
{ |
|
295
|
|
|
$user = new SQLPendingUser($users[$i], $userDataTable); |
|
|
|
|
|
|
296
|
|
|
$err = false; |
|
297
|
|
|
foreach($fieldData as $field=>$data) |
|
298
|
|
|
{ |
|
299
|
|
|
if(strcasecmp($user[$field], $data) !== 0) |
|
300
|
|
|
{ |
|
301
|
|
|
$err = true; break; |
|
302
|
|
|
} |
|
303
|
|
|
} |
|
304
|
|
|
if(!$err) |
|
305
|
|
|
{ |
|
306
|
|
|
array_push($ret, $user); |
|
307
|
|
|
} |
|
308
|
|
|
} |
|
309
|
|
|
return $ret; |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
/** |
|
313
|
|
|
* @param \Data\Filter $filter The filter to read with |
|
314
|
|
|
* @param boolean|array $select The fields to read |
|
315
|
|
|
* @param boolean|integer $top The number of entities to read |
|
316
|
|
|
* @param boolean|integer $skip The number of entities to skip |
|
317
|
|
|
* @param boolean|array $orderby The fields to sort by |
|
318
|
|
|
*/ |
|
319
|
|
|
public function getPendingUsersByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false) |
|
320
|
|
|
{ |
|
321
|
|
|
if($this->pending === false) |
|
322
|
|
|
{ |
|
323
|
|
|
return false; |
|
324
|
|
|
} |
|
325
|
|
|
if($filter !== false && !$filter->contains('hash')) |
|
326
|
|
|
{ |
|
327
|
|
|
return $this->searchPendingUsers($filter, $select, $top, $skip, $orderby); |
|
|
|
|
|
|
328
|
|
|
} |
|
329
|
|
|
$userDataTable = $this->getPendingUserDataTable(); |
|
330
|
|
|
$users = $userDataTable->read($filter, $select, $top, $skip, $orderby); |
|
|
|
|
|
|
331
|
|
|
if($users === false) |
|
332
|
|
|
{ |
|
333
|
|
|
return false; |
|
334
|
|
|
} |
|
335
|
|
|
$count = count($users); |
|
336
|
|
|
for($i = 0; $i < $count; $i++) |
|
337
|
|
|
{ |
|
338
|
|
|
$users[$i] = new SQLPendingUser($users[$i], $userDataTable); |
|
|
|
|
|
|
339
|
|
|
} |
|
340
|
|
|
return $users; |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
public function createPendingUser($user) |
|
344
|
|
|
{ |
|
345
|
|
|
if($this->pending === false) |
|
346
|
|
|
{ |
|
347
|
|
|
return false; |
|
348
|
|
|
} |
|
349
|
|
|
$userDataTable = $this->getPendingUserDataTable(); |
|
350
|
|
|
if(isset($user->password2)) |
|
351
|
|
|
{ |
|
352
|
|
|
unset($user->password2); |
|
353
|
|
|
} |
|
354
|
|
|
$json = json_encode($user); |
|
355
|
|
|
$hash = hash('sha512', $json); |
|
356
|
|
|
$array = array('hash'=>$hash, 'data'=>$json); |
|
357
|
|
|
$ret = $userDataTable->create($array); |
|
358
|
|
|
if($ret !== false) |
|
359
|
|
|
{ |
|
360
|
|
|
$users = $this->getPendingUsersByFilter(new \Data\Filter("hash eq '$hash'")); |
|
361
|
|
|
if($users === false || !isset($users[0])) |
|
362
|
|
|
{ |
|
363
|
|
|
throw new \Exception('Error retreiving user object after successful create!'); |
|
364
|
|
|
} |
|
365
|
|
|
$users[0]->sendEmail(); |
|
366
|
|
|
} |
|
367
|
|
|
return $ret; |
|
368
|
|
|
} |
|
369
|
|
|
|
|
370
|
|
|
public function getTempUserByHash($hash) |
|
|
|
|
|
|
371
|
|
|
{ |
|
372
|
|
|
$users = $this->getPendingUsersByFilter(new \Data\Filter("hash eq '$hash'")); |
|
373
|
|
|
if($users === false || !isset($users[0])) |
|
374
|
|
|
{ |
|
375
|
|
|
return false; |
|
376
|
|
|
} |
|
377
|
|
|
return $users[0]; |
|
378
|
|
|
} |
|
379
|
|
|
} |
|
380
|
|
|
/* vim: set tabstop=4 shiftwidth=4 expandtab: */ |
|
381
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.