generateTokenValues()   A
last analyzed

Complexity

Conditions 6
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 10
cts 10
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 1
nop 1
crap 6
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Passport\Integration;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Doctrine\DBAL\Connection;
22
use Exception;
23
use Limoncello\Contracts\Settings\SettingsProviderInterface;
24
use Limoncello\OAuthServer\Contracts\ClientInterface;
25
use Limoncello\Passport\Contracts\Entities\DatabaseSchemaInterface;
26
use Limoncello\Passport\Contracts\Entities\TokenInterface;
27
use Limoncello\Passport\Contracts\PassportServerIntegrationInterface;
28
use Limoncello\Passport\Entities\Client;
29
use Limoncello\Passport\Entities\DatabaseSchema;
30
use Limoncello\Passport\Package\PassportSettings as C;
31
use Psr\Container\ContainerInterface;
32
use Psr\Http\Message\ResponseInterface;
33
use Psr\Http\Message\UriInterface;
34
use Zend\Diactoros\Response\RedirectResponse;
35
use Zend\Diactoros\Uri;
36
use function array_filter;
37
use function assert;
38
use function bin2hex;
39
use function call_user_func;
40
use function implode;
41
use function is_int;
42
use function is_string;
43
use function password_verify;
44
use function random_bytes;
45
use function uniqid;
46
47
/**
48
 * @package Limoncello\Passport
49
 *
50
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
51
 */
52
abstract class BasePassportServerIntegration implements PassportServerIntegrationInterface
53
{
54
    /** Approval parameter */
55
    const SCOPE_APPROVAL_TYPE = 'type';
56
57
    /** Approval parameter */
58
    const SCOPE_APPROVAL_CLIENT_ID = 'client_id';
59
60
    /** Approval parameter */
61
    const SCOPE_APPROVAL_CLIENT_NAME = 'client_name';
62
63
    /** Approval parameter */
64
    const SCOPE_APPROVAL_REDIRECT_URI = 'redirect_uri';
65
66
    /** Approval parameter */
67
    const SCOPE_APPROVAL_IS_SCOPE_MODIFIED = 'is_scope_modified';
68
69
    /** Approval parameter */
70
    const SCOPE_APPROVAL_SCOPE = 'scope';
71
72
    /** Approval parameter */
73
    const SCOPE_APPROVAL_STATE = 'state';
74
75
    /**
76
     * @var ContainerInterface
77
     */
78
    private $container;
79
80
    /**
81
     * @var array
82
     */
83
    private $settings;
84
85
    /**
86
     * @var string
87
     */
88
    private $defaultClientId;
89
90
    /**
91
     * @var Connection
92
     */
93
    private $connection;
94
95
    /**
96
     * @var DatabaseSchemaInterface
97
     */
98
    private $databaseSchema;
99
100
    /**
101
     * @var string
102
     */
103
    private $approvalUriString;
104
105
    /**
106
     * @var string
107
     */
108
    private $errorUriString;
109
110
    /**
111
     * @var int
112
     */
113
    private $codeExpiration;
114
115
    /**
116
     * @var int
117
     */
118
    private $tokenExpiration;
119
    /**
120
     * @var bool
121
     */
122
    private $isRenewRefreshValue;
123
124
    /**
125
     * @var callable|null
126
     */
127
    private $customPropProvider;
128
129
    /**
130
     * @param ContainerInterface $container
131
     */
132 23
    public function __construct(ContainerInterface $container)
133
    {
134 23
        $this->container = $container;
135 23
        $this->settings  = $container->get(SettingsProviderInterface::class)->get(C::class);
136
137
        /** @var Connection $connection */
138 23
        $connection = $container->get(Connection::class);
139
140
        /** @var callable|null $customPropProvider */
141 23
        $customPropProvider = $this->settings[C::KEY_TOKEN_CUSTOM_PROPERTIES_PROVIDER] ?? null;
142 23
        $wrapper            = $customPropProvider !== null ?
143
            function (TokenInterface $token) use ($container, $customPropProvider): array {
144 5
                return call_user_func($customPropProvider, $container, $token);
145 23
            } : null;
146
147 23
        $this->defaultClientId     = $this->settings[C::KEY_DEFAULT_CLIENT_ID];
148 23
        $this->connection          = $connection;
149 23
        $this->approvalUriString   = $this->settings[C::KEY_APPROVAL_URI_STRING];
150 23
        $this->errorUriString      = $this->settings[C::KEY_ERROR_URI_STRING];
151 23
        $this->codeExpiration      = $this->settings[C::KEY_CODE_EXPIRATION_TIME_IN_SECONDS] ?? 600;
152 23
        $this->tokenExpiration     = $this->settings[C::KEY_TOKEN_EXPIRATION_TIME_IN_SECONDS] ?? 3600;
153 23
        $this->isRenewRefreshValue = $this->settings[C::KEY_RENEW_REFRESH_VALUE_ON_TOKEN_REFRESH] ?? false;
154 23
        $this->customPropProvider  = $wrapper;
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160 7
    public function validateUserId(string $userName, string $password)
161
    {
162 7
        $validator    = $this->settings[C::KEY_USER_CREDENTIALS_VALIDATOR];
163 7
        $nullOrUserId = call_user_func($validator, $this->getContainer(), $userName, $password);
164
165 7
        return $nullOrUserId;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     */
171 6
    public function verifyAllowedUserScope(int $userIdentity, array $scope = null): ?array
172
    {
173 6
        $validator   = $this->settings[C::KEY_USER_SCOPE_VALIDATOR];
174 6
        $nullOrScope = call_user_func($validator, $this->getContainer(), $userIdentity, $scope);
175
176 6
        return $nullOrScope;
177
    }
178
179
    /**
180
     * @inheritdoc
181
     */
182 4
    public function getDefaultClientIdentifier(): string
183
    {
184 4
        return $this->defaultClientId;
185
    }
186
187
    /**
188
     * @inheritdoc
189
     *
190
     * @throws Exception
191
     */
192 1
    public function generateCodeValue(TokenInterface $token): string
193
    {
194 1
        $codeValue = bin2hex(random_bytes(16)) . uniqid();
195
196 1
        assert(is_string($codeValue) === true && empty($codeValue) === false);
197
198 1
        return $codeValue;
199
    }
200
201
    /**
202
     * @inheritdoc
203
     *
204
     * @throws Exception
205
     */
206 6
    public function generateTokenValues(TokenInterface $token): array
207
    {
208 6
        $tokenValue     = bin2hex(random_bytes(16)) . uniqid();
209 6
        $tokenType      = 'bearer';
210 6
        $tokenExpiresIn = $this->getTokenExpirationPeriod();
211 6
        $refreshValue   = bin2hex(random_bytes(16)) . uniqid();
212
213 6
        assert(is_string($tokenValue) === true && empty($tokenValue) === false);
214 6
        assert(is_string($tokenType) === true && empty($tokenType) === false);
215 6
        assert(is_int($tokenExpiresIn) === true && $tokenExpiresIn > 0);
216 6
        assert($refreshValue === null || (is_string($refreshValue) === true && empty($refreshValue) === false));
217
218 6
        return [$tokenValue, $tokenType, $tokenExpiresIn, $refreshValue];
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224 1
    public function getCodeExpirationPeriod(): int
225
    {
226 1
        return $this->codeExpiration;
227
    }
228
229
    /**
230
     * @inheritdoc
231
     */
232 6
    public function getTokenExpirationPeriod(): int
233
    {
234 6
        return $this->tokenExpiration;
235
    }
236
237
    /**
238
     * @inheritdoc
239
     */
240 2
    public function isRenewRefreshValue(): bool
241
    {
242 2
        return $this->isRenewRefreshValue;
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248 5
    public function createInvalidClientAndRedirectUriErrorResponse(): ResponseInterface
249
    {
250 5
        return new RedirectResponse($this->getErrorUriString());
251
    }
252
253
    /** @noinspection PhpTooManyParametersInspection
254
     * @inheritdoc
255
     *
256
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
257
     */
258 5
    public function createAskResourceOwnerForApprovalResponse(
259
        string $type,
260
        ClientInterface $client,
261
        string $redirectUri = null,
262
        bool $isScopeModified = false,
263
        array $scopeList = null,
264
        string $state = null,
265
        array $extraParameters = []
266
    ): ResponseInterface {
267
        /** @var Client $client */
268 5
        assert($client instanceof Client);
269
270
        // TODO think if we can receive objects instead of individual properties
271 5
        $scopeList = empty($scopeList) === true ? null : implode(' ', $scopeList);
272 5
        $filtered  = array_filter([
273 5
            self::SCOPE_APPROVAL_TYPE              => $type,
274 5
            self::SCOPE_APPROVAL_CLIENT_ID         => $client->getIdentifier(),
275 5
            self::SCOPE_APPROVAL_CLIENT_NAME       => $client->getName(),
276 5
            self::SCOPE_APPROVAL_REDIRECT_URI      => $redirectUri,
277 5
            self::SCOPE_APPROVAL_IS_SCOPE_MODIFIED => $isScopeModified,
278 5
            self::SCOPE_APPROVAL_SCOPE             => $scopeList,
279 5
            self::SCOPE_APPROVAL_STATE             => $state,
280
        ], function ($value) {
281 5
            return $value !== null;
282 5
        });
283
284 5
        return new RedirectResponse($this->createRedirectUri($this->getApprovalUriString(), $filtered));
285
    }
286
287
    /**
288
     * @inheritdoc
289
     */
290 1
    public function verifyClientCredentials(ClientInterface $client, string $credentials): bool
291
    {
292
        /** @var \Limoncello\Passport\Contracts\Entities\ClientInterface $client */
293 1
        assert($client instanceof \Limoncello\Passport\Contracts\Entities\ClientInterface);
294
295 1
        return password_verify($credentials, $client->getCredentials());
296
    }
297
298
    /**
299
     * @inheritdoc
300
     */
301 5
    public function getBodyTokenExtraParameters(TokenInterface $token): array
302
    {
303 5
        return $this->customPropProvider !== null ? call_user_func($this->customPropProvider, $token) : [];
304
    }
305
306
    /**
307
     * @param string $uri
308
     * @param array  $data
309
     *
310
     * @return UriInterface
311
     */
312 5
    protected function createRedirectUri(string $uri, array $data): UriInterface
313
    {
314 5
        $query  = http_build_query($data, '', '&', PHP_QUERY_RFC3986);
315 5
        $result = (new Uri($uri))->withQuery($query);
316
317 5
        return $result;
318
    }
319
320
    /**
321
     * @return ContainerInterface
322
     */
323 7
    protected function getContainer(): ContainerInterface
324
    {
325 7
        return $this->container;
326
    }
327
328
    /**
329
     * @return Connection
330
     */
331 19
    protected function getConnection(): Connection
332
    {
333 19
        return $this->connection;
334
    }
335
336
    /**
337
     * @return DatabaseSchemaInterface
338
     */
339 19
    protected function getDatabaseSchema(): DatabaseSchemaInterface
340
    {
341 19
        if ($this->databaseSchema === null) {
342 19
            $this->databaseSchema = new DatabaseSchema();
343
        }
344
345 19
        return $this->databaseSchema;
346
    }
347
348
    /**
349
     * @return string
350
     */
351 5
    protected function getApprovalUriString(): string
352
    {
353 5
        return $this->approvalUriString;
354
    }
355
356
    /**
357
     * @return string
358
     */
359 5
    protected function getErrorUriString(): string
360
    {
361 5
        return $this->errorUriString;
362
    }
363
}
364