Completed
Push — develop ( a2c65d...283ac2 )
by Kristijan
13s
created

Pdo::setAuthorizationCodeWithIdToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 7
1
<?php
2
3
namespace OAuth2\Storage;
4
5
use OAuth2\OpenID\Storage\UserClaimsInterface;
6
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface;
7
8
/**
9
 * Simple PDO storage for all storage types
10
 *
11
 * NOTE: This class is meant to get users started
12
 * quickly. If your application requires further
13
 * customization, extend this class or create your own.
14
 *
15
 * NOTE: Passwords are stored in plaintext, which is never
16
 * a good idea.  Be sure to override this for your application
17
 *
18
 * @author Brent Shaffer <bshafs at gmail dot com>
19
 */
20
class Pdo implements
21
    AuthorizationCodeInterface,
22
    AccessTokenInterface,
23
    ClientCredentialsInterface,
24
    UserCredentialsInterface,
25
    RefreshTokenInterface,
26
    JwtBearerInterface,
27
    ScopeInterface,
28
    PublicKeyInterface,
29
    UserClaimsInterface,
30
    OpenIDAuthorizationCodeInterface
31
{
32
    protected $db;
33
    protected $config;
34
35
    public function __construct($connection, $config = array())
36
    {
37
        if (!$connection instanceof \PDO) {
38
            if (is_string($connection)) {
39
                $connection = array('dsn' => $connection);
40
            }
41
            if (!is_array($connection)) {
42
                throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array');
43
            }
44
            if (!isset($connection['dsn'])) {
45
                throw new \InvalidArgumentException('configuration array must contain "dsn"');
46
            }
47
            // merge optional parameters
48
            $connection = array_merge(array(
49
                'username' => null,
50
                'password' => null,
51
                'options' => array(),
52
            ), $connection);
53
            $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']);
54
        }
55
        $this->db = $connection;
56
57
        // debugging
58
        $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
59
60
        $this->config = array_merge(array(
61
            'client_table' => 'oauth_clients',
62
            'access_token_table' => 'oauth_access_tokens',
63
            'refresh_token_table' => 'oauth_refresh_tokens',
64
            'code_table' => 'oauth_authorization_codes',
65
            'user_table' => 'oauth_users',
66
            'jwt_table'  => 'oauth_jwt',
67
            'jti_table'  => 'oauth_jti',
68
            'scope_table'  => 'oauth_scopes',
69
            'public_key_table'  => 'oauth_public_keys',
70
        ), $config);
71
    }
72
73
    /* OAuth2\Storage\ClientCredentialsInterface */
74
    public function checkClientCredentials($client_id, $client_secret = null)
75
    {
76
        $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
77
        $stmt->execute(compact('client_id'));
78
        $result = $stmt->fetch(\PDO::FETCH_ASSOC);
79
80
        // make this extensible
81
        return $result && $result['client_secret'] == $client_secret;
82
    }
83
84
    public function isPublicClient($client_id)
85
    {
86
        $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
87
        $stmt->execute(compact('client_id'));
88
89
        if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
90
            return false;
91
        }
92
93
        return empty($result['client_secret']);
94
    }
95
96
    /* OAuth2\Storage\ClientInterface */
97
    public function getClientDetails($client_id)
98
    {
99
        $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table']));
100
        $stmt->execute(compact('client_id'));
101
102
        return $stmt->fetch(\PDO::FETCH_ASSOC);
103
    }
104
105
    public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null)
106
    {
107
        // if it exists, update it.
108
        if ($this->getClientDetails($client_id)) {
109
            $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, scope=:scope, user_id=:user_id where client_id=:client_id', $this->config['client_table']));
110
        } else {
111
            $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :scope, :user_id)', $this->config['client_table']));
112
        }
113
114
        return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id'));
115
    }
116
117
    public function checkRestrictedGrantType($client_id, $grant_type)
118
    {
119
        $details = $this->getClientDetails($client_id);
120
        if (isset($details['grant_types'])) {
121
            $grant_types = explode(' ', $details['grant_types']);
122
123
            return in_array($grant_type, (array) $grant_types);
124
        }
125
126
        // if grant_types are not defined, then none are restricted
127
        return true;
128
    }
129
130
    /* OAuth2\Storage\AccessTokenInterface */
131
    public function getAccessToken($access_token)
132
    {
133
        $stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table']));
134
135
        $token = $stmt->execute(compact('access_token'));
0 ignored issues
show
Unused Code introduced by
The assignment to $token is dead and can be removed.
Loading history...
136
        if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
137
            // convert date string back to timestamp
138
            $token['expires'] = strtotime($token['expires']);
139
        }
140
141
        return $token;
142
    }
143
144
    public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
145
    {
146
        // convert expires to datestring
147
        $expires = date('Y-m-d H:i:s', $expires);
148
149
        // if it exists, update it.
150
        if ($this->getAccessToken($access_token)) {
151
            $stmt = $this->db->prepare(sprintf('UPDATE %s SET client_id=:client_id, expires=:expires, user_id=:user_id, scope=:scope where access_token=:access_token', $this->config['access_token_table']));
152
        } else {
153
            $stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table']));
154
        }
155
156
        return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope'));
157
    }
158
159
    public function unsetAccessToken($access_token)
160
    {
161
        $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table']));
162
163
        return $stmt->execute(compact('access_token'));
164
    }
165
166
    /* OAuth2\Storage\AuthorizationCodeInterface */
167
    public function getAuthorizationCode($code)
168
    {
169
        $stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table']));
170
        $stmt->execute(compact('code'));
171
172
        if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) {
173
            // convert date string back to timestamp
174
            $code['expires'] = strtotime($code['expires']);
175
        }
176
177
        return $code;
178
    }
179
180
    public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
181
    {
182
        if (func_num_args() > 6) {
183
            // we are calling with an id token
184
            return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args());
185
        }
186
187
        // convert expires to datestring
188
        $expires = date('Y-m-d H:i:s', $expires);
189
190
        // if it exists, update it.
191
        if ($this->getAuthorizationCode($code)) {
192
            $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table']));
193
        } else {
194
            $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table']));
195
        }
196
197
        return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope'));
198
    }
199
200
    private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null)
201
    {
202
        // convert expires to datestring
203
        $expires = date('Y-m-d H:i:s', $expires);
204
205
        // if it exists, update it.
206
        if ($this->getAuthorizationCode($code)) {
207
            $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table']));
208
        } else {
209
            $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table']));
210
        }
211
212
        return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'));
213
    }
214
215
    public function expireAuthorizationCode($code)
216
    {
217
        $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table']));
218
219
        return $stmt->execute(compact('code'));
220
    }
221
222
    /* OAuth2\Storage\UserCredentialsInterface */
223
    public function checkUserCredentials($username, $password)
224
    {
225
        if ($user = $this->getUser($username)) {
226
            return $this->checkPassword($user, $password);
227
        }
228
229
        return false;
230
    }
231
232
    public function getUserDetails($username)
233
    {
234
        return $this->getUser($username);
235
    }
236
237
    /* UserClaimsInterface */
238
    public function getUserClaims($user_id, $claims)
239
    {
240
        if (!$userDetails = $this->getUserDetails($user_id)) {
241
            return false;
242
        }
243
244
        $claims = explode(' ', trim($claims));
245
        $userClaims = array();
246
247
        // for each requested claim, if the user has the claim, set it in the response
248
        $validClaims = explode(' ', self::VALID_CLAIMS);
249
        foreach ($validClaims as $validClaim) {
250
            if (in_array($validClaim, $claims)) {
251
                if ($validClaim == 'address') {
252
                    // address is an object with subfields
253
                    $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails);
254
                } else {
255
                    $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails));
256
                }
257
            }
258
        }
259
260
        return $userClaims;
261
    }
262
263
    protected function getUserClaim($claim, $userDetails)
264
    {
265
        $userClaims = array();
266
        $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim)));
267
        $claimValues = explode(' ', $claimValuesString);
268
269
        foreach ($claimValues as $value) {
270
            $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null;
271
        }
272
273
        return $userClaims;
274
    }
275
276
    /* OAuth2\Storage\RefreshTokenInterface */
277
    public function getRefreshToken($refresh_token)
278
    {
279
        $stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
280
281
        $token = $stmt->execute(compact('refresh_token'));
0 ignored issues
show
Unused Code introduced by
The assignment to $token is dead and can be removed.
Loading history...
282
        if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) {
283
            // convert expires to epoch time
284
            $token['expires'] = strtotime($token['expires']);
285
        }
286
287
        return $token;
288
    }
289
290
    public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
291
    {
292
        // convert expires to datestring
293
        $expires = date('Y-m-d H:i:s', $expires);
294
295
        $stmt = $this->db->prepare(sprintf('INSERT INTO %s (refresh_token, client_id, user_id, expires, scope) VALUES (:refresh_token, :client_id, :user_id, :expires, :scope)', $this->config['refresh_token_table']));
296
297
        return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope'));
298
    }
299
300
    public function unsetRefreshToken($refresh_token)
301
    {
302
        $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table']));
303
304
        return $stmt->execute(compact('refresh_token'));
305
    }
306
307
    // plaintext passwords are bad!  Override this for your application
308
    protected function checkPassword($user, $password)
309
    {
310
        return $user['password'] == sha1($password);
311
    }
312
313
    public function getUser($username)
314
    {
315
        $stmt = $this->db->prepare($sql = sprintf('SELECT * from %s where username=:username', $this->config['user_table']));
316
        $stmt->execute(array('username' => $username));
317
318
        if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) {
319
            return false;
320
        }
321
322
        // the default behavior is to use "username" as the user_id
323
        return array_merge(array(
324
            'user_id' => $username
325
        ), $userInfo);
326
    }
327
328
    public function setUser($username, $password, $firstName = null, $lastName = null)
329
    {
330
        // do not store in plaintext
331
        $password = sha1($password);
332
333
        // if it exists, update it.
334
        if ($this->getUser($username)) {
335
            $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET password=:password, first_name=:firstName, last_name=:lastName where username=:username', $this->config['user_table']));
336
        } else {
337
            $stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table']));
338
        }
339
340
        return $stmt->execute(compact('username', 'password', 'firstName', 'lastName'));
341
    }
342
343
    /* ScopeInterface */
344
    public function scopeExists($scope)
345
    {
346
        $scope = explode(' ', $scope);
347
        $whereIn = implode(',', array_fill(0, count($scope), '?'));
348
        $stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn));
349
        $stmt->execute($scope);
350
351
        if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
352
            return $result['count'] == count($scope);
353
        }
354
355
        return false;
356
    }
357
358
    public function getDefaultScope($client_id = null)
359
    {
360
        $stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table']));
361
        $stmt->execute(array('is_default' => true));
362
363
        if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) {
364
            $defaultScope = array_map(function ($row) {
365
                return $row['scope'];
366
            }, $result);
367
368
            return implode(' ', $defaultScope);
369
        }
370
371
        return null;
372
    }
373
374
    /* JWTBearerInterface */
375
    public function getClientKey($client_id, $subject)
376
    {
377
        $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table']));
378
379
        $stmt->execute(array('client_id' => $client_id, 'subject' => $subject));
380
381
        return $stmt->fetchColumn();
382
    }
383
384
    public function getClientScope($client_id)
385
    {
386
        if (!$clientDetails = $this->getClientDetails($client_id)) {
387
            return false;
388
        }
389
390
        if (isset($clientDetails['scope'])) {
391
            return $clientDetails['scope'];
392
        }
393
394
        return null;
395
    }
396
397
    public function getJti($client_id, $subject, $audience, $expires, $jti)
398
    {
399
        $stmt = $this->db->prepare($sql = sprintf('SELECT * FROM %s WHERE issuer=:client_id AND subject=:subject AND audience=:audience AND expires=:expires AND jti=:jti', $this->config['jti_table']));
400
401
        $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
402
403
        if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
404
            return array(
405
                'issuer' => $result['issuer'],
406
                'subject' => $result['subject'],
407
                'audience' => $result['audience'],
408
                'expires' => $result['expires'],
409
                'jti' => $result['jti'],
410
            );
411
        }
412
413
        return null;
414
    }
415
416
    public function setJti($client_id, $subject, $audience, $expires, $jti)
417
    {
418
        $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table']));
419
420
        return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti'));
421
    }
422
423
    /* PublicKeyInterface */
424
    public function getPublicKey($client_id = null)
425
    {
426
        $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
427
428
        $stmt->execute(compact('client_id'));
429
        if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
430
            return $result['public_key'];
431
        }
432
    }
433
434
    public function getPrivateKey($client_id = null)
435
    {
436
        $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
437
438
        $stmt->execute(compact('client_id'));
439
        if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
440
            return $result['private_key'];
441
        }
442
    }
443
444
    public function getEncryptionAlgorithm($client_id = null)
445
    {
446
        $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table']));
447
448
        $stmt->execute(compact('client_id'));
449
        if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) {
450
            return $result['encryption_algorithm'];
451
        }
452
453
        return 'RS256';
454
    }
455
456
    /**
457
     * DDL to create OAuth2 database and tables for PDO storage
458
     *
459
     * @see https://github.com/dsquier/oauth2-server-php-mysql
460
     */
461
    public function getBuildSql($dbName = 'oauth2_server_php')
0 ignored issues
show
Unused Code introduced by
The parameter $dbName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

461
    public function getBuildSql(/** @scrutinizer ignore-unused */ $dbName = 'oauth2_server_php')

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
462
    {
463
        $sql = "
464
        CREATE TABLE {$this->config['client_table']} (
465
          client_id             VARCHAR(80)   NOT NULL,
466
          client_secret         VARCHAR(80)   NOT NULL,
467
          redirect_uri          VARCHAR(2000),
468
          grant_types           VARCHAR(80),
469
          scope                 VARCHAR(4000),
470
          user_id               VARCHAR(80),
471
          PRIMARY KEY (client_id)
472
        );
473
474
        CREATE TABLE {$this->config['access_token_table']} (
475
          access_token         VARCHAR(40)    NOT NULL,
476
          client_id            VARCHAR(80)    NOT NULL,
477
          user_id              VARCHAR(80),
478
          expires              TIMESTAMP      NOT NULL,
479
          scope                VARCHAR(4000),
480
          PRIMARY KEY (access_token)
481
        );
482
483
        CREATE TABLE {$this->config['code_table']} (
484
          authorization_code  VARCHAR(40)    NOT NULL,
485
          client_id           VARCHAR(80)    NOT NULL,
486
          user_id             VARCHAR(80),
487
          redirect_uri        VARCHAR(2000),
488
          expires             TIMESTAMP      NOT NULL,
489
          scope               VARCHAR(4000),
490
          id_token            VARCHAR(1000),
491
          PRIMARY KEY (authorization_code)
492
        );
493
494
        CREATE TABLE {$this->config['refresh_token_table']} (
495
          refresh_token       VARCHAR(40)    NOT NULL,
496
          client_id           VARCHAR(80)    NOT NULL,
497
          user_id             VARCHAR(80),
498
          expires             TIMESTAMP      NOT NULL,
499
          scope               VARCHAR(4000),
500
          PRIMARY KEY (refresh_token)
501
        );
502
503
        CREATE TABLE {$this->config['user_table']} (
504
          username            VARCHAR(80),
505
          password            VARCHAR(80),
506
          first_name          VARCHAR(80),
507
          last_name           VARCHAR(80),
508
          email               VARCHAR(80),
509
          email_verified      BOOLEAN,
510
          scope               VARCHAR(4000)
511
        );
512
513
        CREATE TABLE {$this->config['scope_table']} (
514
          scope               VARCHAR(80)  NOT NULL,
515
          is_default          BOOLEAN,
516
          PRIMARY KEY (scope)
517
        );
518
519
        CREATE TABLE {$this->config['jwt_table']} (
520
          client_id           VARCHAR(80)   NOT NULL,
521
          subject             VARCHAR(80),
522
          public_key          VARCHAR(2000) NOT NULL
523
        );
524
525
        CREATE TABLE {$this->config['jti_table']} (
526
          issuer              VARCHAR(80)   NOT NULL,
527
          subject             VARCHAR(80),
528
          audiance            VARCHAR(80),
529
          expires             TIMESTAMP     NOT NULL,
530
          jti                 VARCHAR(2000) NOT NULL
531
        );
532
533
        CREATE TABLE {$this->config['public_key_table']} (
534
          client_id            VARCHAR(80),
535
          public_key           VARCHAR(2000),
536
          private_key          VARCHAR(2000),
537
          encryption_algorithm VARCHAR(100) DEFAULT 'RS256'
538
        )
539
";
540
541
        return $sql;
542
    }
543
}
544