Completed
Push — master ( c18000...efec97 )
by Anton
12s
created

Table::generateToken()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 16
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 30
rs 8.8571
ccs 16
cts 16
cp 1
crap 1
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
        self::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
        self::delete(
133
            [
134
                'userId' => Auth::getIdentity()->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
135
                'foreignKey' => Auth::getIdentity()->login,
0 ignored issues
show
Bug introduced by
Accessing login on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
136
                'provider' => self::PROVIDER_COOKIE,
137
                'tokenType' => self::TYPE_ACCESS,
138
            ]
139
        );
140
141
        $row = new Row();
142
        $row->userId = Auth::getIdentity()->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
143
        $row->foreignKey = Auth::getIdentity()->login;
0 ignored issues
show
Bug introduced by
Accessing login on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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(Auth::getIdentity()->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
149
        $row->token = hash('md5', $row->tokenSecret . $hash);
150
151
        $row->save();
152
153
        Response::setCookie('rToken', $hash, time() + $ttl, '/');
154
        Response::setCookie('rId', Auth::getIdentity()->id, time() + $ttl, '/');
0 ignored issues
show
Bug introduced by
Accessing id on the interface Bluz\Auth\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
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
        self::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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

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