Completed
Push — master ( eb16f0...b029a9 )
by Patrick
03:00
created

Auth/class.SQLAuthenticator.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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);
0 ignored issues
show
$firstFilter is of type object<Data\Filter>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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);
0 ignored issues
show
It seems like $userDataTable defined by $this->getPendingUserDataTable() on line 283 can also be of type object<Data\DataTable>; however, Auth\SQLPendingUser::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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);
0 ignored issues
show
$filter is of type object<Data\Filter>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
It seems like $select defined by parameter $select on line 319 can also be of type array; however, Data\DataTable::read() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $top defined by parameter $top on line 319 can also be of type integer; however, Data\DataTable::read() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $skip defined by parameter $skip on line 319 can also be of type integer; however, Data\DataTable::read() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $orderby defined by parameter $orderby on line 319 can also be of type array; however, Data\DataTable::read() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
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);
0 ignored issues
show
It seems like $userDataTable defined by $this->getPendingUserDataTable() on line 329 can also be of type object<Data\DataTable>; however, Auth\SQLPendingUser::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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