Test Setup Failed
Push — master ( bced77...be54f8 )
by Evgeny
05:41
created

JwtAuthenticate::_decode()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace CakeDC\Api\Service\Auth\Authenticate;
4
5
use CakeDC\Api\Service\Action\Action;
6
use Cake\Core\Configure;
7
use Cake\Http\Exception\UnauthorizedException;
8
use Cake\Http\Response;
9
use Cake\Http\ServerRequest;
10
use Cake\Utility\Security;
11
use Exception;
12
use Firebase\JWT\JWT;
13
14
/**
15
 * An authentication adapter for authenticating using JSON Web Tokens.
16
 *
17
 * ```
18
 *  $config[
19
 *      'CakeDC/Api.Jwt' => [
20
 *          'header' => 'authorization',
21
 *          'prefix' => 'bearer',
22
 *          'parameter' => 'token',
23
 *          'userModel' => 'Users',
24
 *          'fields' => [
25
 *              'username' => 'id'
26
 *          ],
27
 *      ]
28
 *  ]);
29
 * ```
30
 *
31
 * @copyright 2015-2018 ADmad
32
 * @license MIT
33
 *
34
 * This Auth adapter was modified and adapted for this App
35
 *
36
 * @see http://jwt.io
37
 * @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token
38
 */
39
class JwtAuthenticate extends BaseAuthenticate
40
{
41
    /**
42
     * Parsed token.
43
     *
44
     * @var string|null
45
     */
46
    protected $_token;
47
48
    /**
49
     * Payload data.
50
     *
51
     * @var object|null
52
     */
53
    protected $_payload;
54
55
    /**
56
     * Exception.
57
     *
58
     * @var \Exception
59
     */
60
    protected $_error;
61
62
    /**
63
     * Constructor.
64
     *
65
     * Settings for this object.
66
     *
67
     * - `header` - Header name to check. Defaults to `'authorization'`.
68
     * - `prefix` - Token prefix. Defaults to `'bearer'`.
69
     * - `parameter` - The url parameter name of the token. Defaults to `token`.
70
     *   First $_SERVER['HTTP_AUTHORIZATION'] is checked for token value.
71
     *   Its value should be of form "Bearer <token>". If empty this query string
72
     *   paramater is checked.
73
     * - `allowedAlgs` - List of supported verification algorithms.
74
     *   Defaults to ['HS256']. See API of JWT::decode() for more info.
75
     * - `queryDatasource` - Boolean indicating whether the `sub` claim of JWT
76
     *   token should be used to query the user model and get user record. If
77
     *   set to `false` JWT's payload is directly retured. Defaults to `true`.
78
     * - `userModel` - The model name of users, defaults to `Users`.
79
     * - `fields` - Key `username` denotes the identifier field for fetching user
80
     *   record. The `sub` claim of JWT must contain identifier value.
81
     *   Defaults to ['username' => 'id'].
82
     * - `finder` - Finder method.
83
     * - `unauthenticatedException` - Fully namespaced exception name. Exception to
84
     *   throw if authentication fails. Set to false to do nothing.
85
     *   Defaults to '\Cake\Htttp\Exception\UnauthorizedException'.
86
     * - `key` - The key, or map of keys used to decode JWT. If not set, value
87
     *   of Security::salt() will be used.
88
     *
89
     * @param Action $action AbstractClass with implementations
90
     *   used on this request.
91
     * @param array $config Array of config to use.
92
     */
93
    public function __construct(Action $action, array $config = [])
94
    {
95
        $config = [
96
            'header' => 'authorization',
97
            'prefix' => 'bearer',
98
            'parameter' => 'token',
99
            'queryDatasource' => true,
100
            'fields' => ['username' => 'id'],
101
            'unauthenticatedException' => UnauthorizedException::class,
102
            'key' => null,
103
        ];
104
105
        if (!class_exists(UnauthorizedException::class)) {
106
            $config['unauthenticatedException'] = 'Cake\Network\Exception\UnauthorizedException';
107
        }
108
109
        $this->setConfig($config);
110
111
        if (empty($config['allowedAlgs'])) {
112
            $config['allowedAlgs'] = ['HS256'];
113
        }
114
115
        parent::__construct($action, $config);
116
    }
117
118
    /**
119
     * Get user record based on info available in JWT.
120
     *
121
     * @param \Cake\Http\ServerRequest $request The request object.
122
     * @param \Cake\Http\Response $response Response object.
123
     *
124
     * @throws Exception
125
     *
126
     * @return bool|array User record array or false on failure.
127
     */
128
    public function authenticate(ServerRequest $request, Response $response)
129
    {
130
        return $this->getUser($request);
131
    }
132
133
    /**
134
     * Get user record based on info available in JWT.
135
     *
136
     * @param \Cake\Http\ServerRequest $request Request object.
137
     *
138
     * @throws Exception
139
     *
140
     * @return bool|array User record array or false on failure.
141
     */
142
    public function getUser(ServerRequest $request)
143
    {
144
        $payload = $this->getPayload($request);
145
146
        if (empty($payload)) {
147
            return false;
148
        }
149
150
        if (!$this->_config['queryDatasource']) {
151
            return json_decode(json_encode($payload), true);
152
        }
153
154
        if (!isset($payload->sub)) {
155
            return false;
156
        }
157
158
        $user = $this->_findUser($payload->sub);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_findUser($payload->sub); of type boolean|array adds the type array to the return on line 165 which is incompatible with the return type of the parent method CakeDC\Api\Service\Auth\...seAuthenticate::getUser of type boolean.
Loading history...
159
        if (!$user) {
160
            return false;
161
        }
162
163
        unset($user[$this->_config['fields']['password']]);
164
165
        return $user;
166
    }
167
168
    /**
169
     * Get payload data.
170
     *
171
     * @param \Cake\Http\ServerRequest|null $request Request instance or null
172
     *
173
     * @throws Exception
174
     *
175
     * @return object|null Payload object on success, null on failurec
176
     */
177
    public function getPayload($request = null)
178
    {
179
        if (!$request) {
180
            return $this->_payload;
181
        }
182
183
        $payload = null;
184
185
        $token = $this->getToken($request);
186
        if ($token) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $token of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
187
            $payload = $this->_decode($token);
188
        }
189
190
        return $this->_payload = $payload;
191
    }
192
193
    /**
194
     * Get token from header or query string.
195
     *
196
     * @param \Cake\Http\ServerRequest|null $request Request object.
197
     *
198
     * @return string|null Token string if found else null.
199
     */
200
    public function getToken($request = null)
201
    {
202
        $config = $this->_config;
203
204
        if ($request === null) {
205
            return $this->_token;
206
        }
207
208
        $header = $request->getHeaderLine($config['header']);
209
        if ($header && stripos($header, $config['prefix']) === 0) {
210
            return $this->_token = str_ireplace($config['prefix'] . ' ', '', $header);
211
        }
212
213
        if (!empty($this->_config['parameter'])) {
214
            $token = $request->getQuery($this->_config['parameter']);
215
            if ($token !== null) {
216
                $token = (string)$token;
217
            }
218
219
            return $this->_token = $token;
220
        }
221
222
        return $this->_token;
223
    }
224
225
    /**
226
     * Decode JWT token.
227
     *
228
     * @param string $token JWT token to decode.
229
     *
230
     * @throws Exception
231
     *
232
     * @return object|null The JWT's payload as a PHP object, null on failure.
233
     */
234
    protected function _decode($token)
235
    {
236
        $config = $this->_config;
237
        try {
238
            $payload = JWT::decode(
239
                $token,
240
                $config['key'] ?: Security::getSalt(),
241
                $config['allowedAlgs']
242
            );
243
244
            return $payload;
245
        } catch (Exception $e) {
246
            if (Configure::read('debug')) {
247
                throw $e;
248
            }
249
            $this->_error = $e;
250
        }
251
    }
252
253
    /**
254
     * Handles an unauthenticated access attempt. Depending on value of config
255
     * `unauthenticatedException` either throws the specified exception or returns
256
     * null.
257
     *
258
     * @param \Cake\Http\ServerRequest $request A request object.
259
     * @param \Cake\Http\Response $response A response object.
260
     *
261
     * @throws \Cake\Http\Exception\UnauthorizedException Or any other
262
     *   configured exception.
263
     *
264
     * @return void
265
     */
266
    public function unauthenticated(ServerRequest $request, Response $response)
267
    {
268
        if (!$this->_config['unauthenticatedException']) {
269
            return;
270
        }
271
272
        $message = $this->_error
273
            ? $this->_error->getMessage()
274
            : $this->_registry->get('Auth')->getConfig('authError');
0 ignored issues
show
Bug introduced by
The property _registry does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
275
276
        $exception = new $this->_config['unauthenticatedException']($message);
277
        throw $exception;
278
    }
279
}
280