Completed
Push — master ( 96a86d...9a9433 )
by Neomerx
02:30
created

TokenRepository::readByUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 3
1
<?php namespace Limoncello\Passport\Repositories;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use DateInterval;
20
use DateTimeImmutable;
21
use Doctrine\DBAL\Query\QueryBuilder;
22
use Limoncello\Passport\Contracts\Entities\ScopeInterface;
23
use Limoncello\Passport\Contracts\Entities\TokenInterface;
24
use Limoncello\Passport\Contracts\Repositories\TokenRepositoryInterface;
25
use PDO;
26
27
/**
28
 * @package Limoncello\Passport
29
 *
30
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
31
 */
32
abstract class TokenRepository extends BaseRepository implements TokenRepositoryInterface
33
{
34
    /**
35
     * @inheritdoc
36
     *
37
     * @SuppressWarnings(PHPMD.ElseExpression)
38
     */
39
    public function createCode(TokenInterface $code): TokenInterface
40
    {
41
        $now    = new DateTimeImmutable();
42
        $scheme = $this->getDatabaseScheme();
43
        $values = [
44
            $scheme->getTokensClientIdentityColumn() => $code->getClientIdentifier(),
45
            $scheme->getTokensUserIdentityColumn()   => $code->getUserIdentifier(),
46
            $scheme->getTokensCodeColumn()           => $code->getCode(),
47
            $scheme->getTokensIsScopeModified()      => $code->isScopeModified(),
48
            $scheme->getTokensCodeCreatedAtColumn()  => $now,
49
        ];
50
51
        $tokenIdentifier = null;
52 View Code Duplication
        if (empty($scopeIdentifiers = $code->getScopeIdentifiers()) === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
53
            $this->inTransaction(function () use ($values, $scopeIdentifiers, &$tokenIdentifier) {
54
                $tokenIdentifier = $this->createResource($values);
55
                $this->bindScopeIdentifiers($tokenIdentifier, $scopeIdentifiers);
56
            });
57
        } else {
58
            $tokenIdentifier = $this->createResource($values);
59
        }
60
61
        $code->setIdentifier($tokenIdentifier)->setCodeCreatedAt($now);
62
63
        return $code;
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function assignValuesToCode(TokenInterface $token, int $expirationInSeconds)
70
    {
71
        $query = $this->getConnection()->createQueryBuilder();
72
73
        $now             = new DateTimeImmutable();
74
        $dbNow           = $this->createTypedParameter($query, $now);
75
        $earliestExpired = $now->sub(new DateInterval("PT{$expirationInSeconds}S"));
76
        $scheme          = $this->getDatabaseScheme();
77
        $query
78
            ->update($this->getTableNameForWriting())
79
            ->where($scheme->getTokensCodeColumn() . '=' . $this->createTypedParameter($query, $token->getCode()))
80
            ->andWhere(
81
                $scheme->getTokensCodeCreatedAtColumn() . '>' . $this->createTypedParameter($query, $earliestExpired)
82
            )
83
            ->set($scheme->getTokensValueColumn(), $this->createTypedParameter($query, $token->getValue()))
84
            ->set($scheme->getTokensTypeColumn(), $this->createTypedParameter($query, $token->getType()))
85
            ->set($scheme->getTokensValueCreatedAtColumn(), $dbNow);
86
87 View Code Duplication
        if ($token->getRefreshValue() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
88
            $query
89
                ->set($scheme->getTokensRefreshColumn(), $this->createTypedParameter($query, $token->getRefreshValue()))
90
                ->set($scheme->getTokensRefreshCreatedAtColumn(), $dbNow);
91
        }
92
93
        $numberOfUpdated = $query->execute();
94
        assert(is_int($numberOfUpdated) === true);
95
    }
96
97
    /**
98
     * @inheritdoc
99
     *
100
     * @SuppressWarnings(PHPMD.ElseExpression)
101
     */
102
    public function createToken(TokenInterface $token): TokenInterface
103
    {
104
        $now        = new DateTimeImmutable();
105
        $scheme     = $this->getDatabaseScheme();
106
        $hasRefresh = $token->getRefreshValue() !== null;
107
        $values     = $hasRefresh === false ? [
108
            $scheme->getTokensClientIdentityColumn()   => $token->getClientIdentifier(),
109
            $scheme->getTokensUserIdentityColumn()     => $token->getUserIdentifier(),
110
            $scheme->getTokensValueColumn()            => $token->getValue(),
111
            $scheme->getTokensTypeColumn()             => $token->getType(),
112
            $scheme->getTokensIsScopeModified()        => $token->isScopeModified(),
113
            $scheme->getTokensIsEnabledColumn()        => $token->isEnabled(),
114
            $scheme->getTokensValueCreatedAtColumn()   => $now,
115
        ] : [
116
            $scheme->getTokensClientIdentityColumn()   => $token->getClientIdentifier(),
117
            $scheme->getTokensUserIdentityColumn()     => $token->getUserIdentifier(),
118
            $scheme->getTokensValueColumn()            => $token->getValue(),
119
            $scheme->getTokensTypeColumn()             => $token->getType(),
120
            $scheme->getTokensIsScopeModified()        => $token->isScopeModified(),
121
            $scheme->getTokensIsEnabledColumn()        => $token->isEnabled(),
122
            $scheme->getTokensValueCreatedAtColumn()   => $now,
123
            $scheme->getTokensRefreshColumn()          => $token->getRefreshValue(),
124
            $scheme->getTokensRefreshCreatedAtColumn() => $now,
125
        ];
126
127
        $tokenIdentifier = null;
128 View Code Duplication
        if (empty($scopeIdentifiers = $token->getScopeIdentifiers()) === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
129
            $this->inTransaction(function () use ($values, $scopeIdentifiers, &$tokenIdentifier) {
130
                $tokenIdentifier = $this->createResource($values);
131
                $this->bindScopeIdentifiers($tokenIdentifier, $scopeIdentifiers);
132
            });
133
        } else {
134
            $tokenIdentifier = $this->createResource($values);
135
        }
136
137
        $token->setIdentifier($tokenIdentifier)->setValueCreatedAt($now);
138
        if ($hasRefresh === true) {
139
            $token->setRefreshCreatedAt($now);
140
        }
141
142
        return $token;
143
    }
144
145
    /**
146
     * @inheritdoc
147
     */
148 View Code Duplication
    public function bindScopes(int $identifier, array $scopes)
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...
149
    {
150
        $scopeIdentifiers = [];
151
        foreach ($scopes as $scope) {
152
            /** @var ScopeInterface $scope */
153
            assert($scope instanceof ScopeInterface);
154
            $scopeIdentifiers[] = $scope->getIdentifier();
155
        }
156
157
        $this->bindScopeIdentifiers($identifier, $scopeIdentifiers);
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163
    public function bindScopeIdentifiers(int $identifier, array $scopeIdentifiers)
164
    {
165
        if (empty($scopeIdentifiers) === false) {
166
            $scheme = $this->getDatabaseScheme();
167
            $this->createBelongsToManyRelationship(
168
                $identifier,
169
                $scopeIdentifiers,
170
                $scheme->getTokensScopesTable(),
171
                $scheme->getTokensScopesTokenIdentityColumn(),
172
                $scheme->getTokensScopesScopeIdentityColumn()
173
            );
174
        }
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180
    public function unbindScopes(int $identifier)
181
    {
182
        $scheme = $this->getDatabaseScheme();
183
        $this->deleteBelongsToManyRelationshipIdentifiers(
184
            $scheme->getTokensScopesTable(),
185
            $scheme->getTokensScopesTokenIdentityColumn(),
186
            $identifier
187
        );
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193
    public function read(int $identifier)
194
    {
195
        return $this->readResource($identifier);
196
    }
197
198
    /**
199
     * @inheritdoc
200
     */
201
    public function readByCode(string $code, int $expirationInSeconds)
202
    {
203
        $scheme = $this->getDatabaseScheme();
204
        return $this->readEnabledTokenByColumnWithExpirationCheck(
205
            $code,
206
            $scheme->getTokensCodeColumn(),
207
            $expirationInSeconds,
208
            $scheme->getTokensCodeCreatedAtColumn()
209
        );
210
    }
211
212
    /**
213
     * @inheritdoc
214
     */
215
    public function readByValue(string $tokenValue, int $expirationInSeconds)
216
    {
217
        $scheme = $this->getDatabaseScheme();
218
        return $this->readEnabledTokenByColumnWithExpirationCheck(
219
            $tokenValue,
220
            $scheme->getTokensValueColumn(),
221
            $expirationInSeconds,
222
            $scheme->getTokensValueCreatedAtColumn()
223
        );
224
    }
225
226
    /**
227
     * @inheritdoc
228
     */
229
    public function readByRefresh(string $refreshValue, int $expirationInSeconds)
230
    {
231
        $scheme = $this->getDatabaseScheme();
232
        return $this->readEnabledTokenByColumnWithExpirationCheck(
233
            $refreshValue,
234
            $scheme->getTokensRefreshColumn(),
235
            $expirationInSeconds,
236
            $scheme->getTokensRefreshCreatedAtColumn()
237
        );
238
    }
239
240
    /**
241
     * @inheritdoc
242
     */
243
    public function readByUser(int $userId, int $expirationInSeconds, int $limit = null): array
244
    {
245
        $scheme = $this->getDatabaseScheme();
246
        /** @var TokenInterface[] $tokens */
247
        $tokens = $this->readEnabledTokensByColumnWithExpirationCheck(
248
            $userId,
249
            $scheme->getTokensUserIdentityColumn(),
250
            $expirationInSeconds,
251
            $scheme->getTokensValueCreatedAtColumn(),
252
            ['*'],
253
            $limit
254
        );
255
256
        return $tokens;
257
    }
258
259
    /**
260
     * @inheritdoc
261
     */
262
    public function readScopeIdentifiers(int $identifier): array
263
    {
264
        $scheme = $this->getDatabaseScheme();
265
        return $this->readBelongsToManyRelationshipIdentifiers(
266
            $identifier,
267
            $scheme->getTokensScopesTable(),
268
            $scheme->getTokensScopesTokenIdentityColumn(),
269
            $scheme->getTokensScopesScopeIdentityColumn()
270
        );
271
    }
272
273
    /**
274
     * @inheritdoc
275
     */
276
    public function updateValues(TokenInterface $token)
277
    {
278
        $query = $this->getConnection()->createQueryBuilder();
279
280
        $scheme = $this->getDatabaseScheme();
281
        $now    = new DateTimeImmutable();
282
        $dbNow  = $this->createTypedParameter($query, $now);
283
        $query
284
            ->update($this->getTableNameForWriting())
285
            ->where($this->getPrimaryKeyName() . '=' . $this->createTypedParameter($query, $token->getIdentifier()))
286
            ->set($scheme->getTokensValueColumn(), $this->createTypedParameter($query, $token->getValue()))
287
            ->set($scheme->getTokensValueCreatedAtColumn(), $dbNow);
288 View Code Duplication
        if ($token->getRefreshValue() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
289
            $query
290
                ->set($scheme->getTokensRefreshColumn(), $this->createTypedParameter($query, $token->getRefreshValue()))
291
                ->set($scheme->getTokensRefreshCreatedAtColumn(), $dbNow);
292
        }
293
294
        $numberOfUpdated = $query->execute();
295
        assert(is_int($numberOfUpdated) === true);
296
        if ($numberOfUpdated > 0) {
297
            $token->setValueCreatedAt($now);
298
            if ($token->getRefreshValue() !== null) {
299
                $token->setRefreshCreatedAt($now);
300
            }
301
        }
302
    }
303
304
    /**
305
     * @inheritdoc
306
     */
307
    public function delete(int $identifier)
308
    {
309
        $this->deleteResource($identifier);
310
    }
311
312
    /**
313
     * @inheritdoc
314
     */
315
    public function disable(int $identifier)
316
    {
317
        $query = $this->getConnection()->createQueryBuilder();
318
319
        $scheme = $this->getDatabaseScheme();
320
        $query
321
            ->update($this->getTableNameForWriting())
322
            ->where($this->getPrimaryKeyName() . '=' . $this->createTypedParameter($query, $identifier))
323
            ->set($scheme->getTokensIsEnabledColumn(), $this->createTypedParameter($query, false));
324
325
        $numberOfUpdated = $query->execute();
326
        assert(is_int($numberOfUpdated) === true);
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332
    protected function getTableNameForWriting(): string
333
    {
334
        return $this->getDatabaseScheme()->getTokensTable();
335
    }
336
337
    /**
338
     * @inheritdoc
339
     */
340
    protected function getPrimaryKeyName(): string
341
    {
342
        return $this->getDatabaseScheme()->getTokensIdentityColumn();
343
    }
344
345
    /**
346
     * @param string $identifier
347
     * @param string $column
348
     * @param int    $expirationInSeconds
349
     * @param string $createdAtColumn
350
     * @param array  $columns
351
     *
352
     * @return TokenInterface|null
353
     */
354
    protected function readEnabledTokenByColumnWithExpirationCheck(
355
        string $identifier,
356
        string $column,
357
        int $expirationInSeconds,
358
        string $createdAtColumn,
359
        array $columns = ['*']
360
    ) {
361
        $query = $this->createEnabledTokenByColumnWithExpirationCheckQuery(
362
            $identifier,
363
            $column,
364
            $expirationInSeconds,
365
            $createdAtColumn,
366
            $columns
367
        );
368
369
        $statement = $query->execute();
370
        $statement->setFetchMode(PDO::FETCH_CLASS, $this->getClassName());
371
        $result = $statement->fetch();
372
373
        return $result === false ? null : $result;
374
    }
375
376
    /** @noinspection PhpTooManyParametersInspection
377
     * @param string   $identifier
378
     * @param string   $column
379
     * @param int      $expirationInSeconds
380
     * @param string   $createdAtColumn
381
     * @param array    $columns
382
     * @param int|null $limit
383
     *
384
     * @return array
385
     */
386
    protected function readEnabledTokensByColumnWithExpirationCheck(
387
        string $identifier,
388
        string $column,
389
        int $expirationInSeconds,
390
        string $createdAtColumn,
391
        array $columns = ['*'],
392
        int $limit = null
393
    ): array {
394
        $query = $this->createEnabledTokenByColumnWithExpirationCheckQuery(
395
            $identifier,
396
            $column,
397
            $expirationInSeconds,
398
            $createdAtColumn,
399
            $columns
400
        );
401
        $limit === null ?: $query->setMaxResults($limit);
402
403
        $statement = $query->execute();
404
        $statement->setFetchMode(PDO::FETCH_CLASS, $this->getClassName());
405
406
        $result = [];
407
        while (($token = $statement->fetch()) !== false) {
408
            /** @var TokenInterface $token */
409
            $result[$token->getIdentifier()] = $token;
410
        }
411
412
        return $result;
413
    }
414
415
    /**
416
     * @param string $identifier
417
     * @param string $column
418
     * @param int    $expirationInSeconds
419
     * @param string $createdAtColumn
420
     * @param array  $columns
421
     *
422
     * @return QueryBuilder
423
     */
424
    protected function createEnabledTokenByColumnWithExpirationCheckQuery(
425
        string $identifier,
426
        string $column,
427
        int $expirationInSeconds,
428
        string $createdAtColumn,
429
        array $columns = ['*']
430
    ): QueryBuilder {
431
        $query = $this->getConnection()->createQueryBuilder();
432
        $query = $this->addExpirationCondition(
433
            $query->select($columns)
434
                ->from($this->getTableNameForReading())
435
                ->where($column . '=' . $this->createTypedParameter($query, $identifier))
436
                ->andWhere($query->expr()->eq($this->getDatabaseScheme()->getTokensIsEnabledColumn(), '1')),
437
            $expirationInSeconds,
438
            $createdAtColumn
439
        );
440
441
        return $query;
442
    }
443
444
    /**
445
     * @param QueryBuilder $query
446
     * @param int          $expirationInSeconds
447
     * @param string       $createdAtColumn
448
     *
449
     * @return QueryBuilder
450
     */
451
    protected function addExpirationCondition(
452
        QueryBuilder $query,
453
        int $expirationInSeconds,
454
        string $createdAtColumn
455
    ): QueryBuilder {
456
        $earliestExpired = (new DateTimeImmutable())->sub(new DateInterval("PT{$expirationInSeconds}S"));
457
        $query->andWhere($createdAtColumn . '>' . $this->createTypedParameter($query, $earliestExpired));
458
459
        return $query;
460
    }
461
}
462