Completed
Push — master ( 7c3a00...4fd5ad )
by Neomerx
04:42
created

BasePassportServerIntegration::__construct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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