Passed
Push — master ( 6f2986...60854d )
by Rutger
13:35
created

Oauth2ClientAuthorizationRequest::__serialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 17
ccs 15
cts 15
cp 1
rs 9.7666
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\components\authorization;
4
5
use rhertogh\Yii2Oauth2Server\components\authorization\base\Oauth2BaseClientAuthorizationRequest;
6
use rhertogh\Yii2Oauth2Server\helpers\DiHelper;
7
use rhertogh\Yii2Oauth2Server\helpers\UrlHelper;
8
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\Oauth2ScopeAuthorizationRequestInterface;
9
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface;
10
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ScopeInterface;
11
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2UserClientInterface;
12
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2UserClientScopeInterface;
13
use rhertogh\Yii2Oauth2Server\models\Oauth2Scope;
14
use Yii;
15
use yii\base\InvalidCallException;
16
use yii\helpers\StringHelper;
17
18
class Oauth2ClientAuthorizationRequest extends Oauth2BaseClientAuthorizationRequest
19
{
20
    /**
21
     * @var string|null
22
     */
23
    protected $_requestId = null;
24
25
    /**
26
     * @var string|null
27
     */
28
    protected $_state = null;
29
30
    /**
31
     * @var bool
32
     */
33
    protected $_userAuthenticatedBeforeRequest = false;
34
35
    /**
36
     * @var bool
37
     */
38
    protected $_authenticatedDuringRequest = false;
39
40
    /**
41
     * @var Oauth2ClientInterface|null
42
     */
43
    protected $_client = null;
44
45
    /**
46
     * @var Oauth2ScopeAuthorizationRequestInterface[]|null
47
     */
48
    protected $_scopeAuthorizationRequests = null;
49
50
    /**
51
     * @var Oauth2ScopeInterface[]|null
52
     */
53
    protected $_scopesAppliedByDefaultAutomatically = null;
54
55
    /**
56
     * @inheritDoc
57
     */
58 1
    public function __serialize()
59
    {
60
        return [
61 1
            '_requestId' => $this->_requestId,
62 1
            '_clientIdentifier' => $this->_clientIdentifier,
63 1
            '_state' => $this->_state,
64 1
            '_userIdentifier' => $this->_userIdentifier,
65 1
            '_userAuthenticatedBeforeRequest' => $this->_userAuthenticatedBeforeRequest,
66 1
            '_authenticatedDuringRequest' => $this->_authenticatedDuringRequest,
67 1
            '_authorizeUrl' => $this->_authorizeUrl,
68 1
            '_requestedScopeIdentifiers' => $this->_requestedScopeIdentifiers,
69 1
            '_grantType' => $this->_grantType,
70 1
            '_prompts' => $this->_prompts,
71 1
            '_maxAge' => $this->_maxAge,
72 1
            '_selectedScopeIdentifiers' => $this->_selectedScopeIdentifiers,
73 1
            '_authorizationStatus' => $this->_authorizationStatus,
74 1
            '_isCompleted' => $this->_isCompleted,
75
        ];
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 1
    public function __unserialize($data)
82
    {
83 1
        foreach ($data as $name => $value) {
84 1
            $this->$name = $value;
85
        }
86
    }
87
88
    /**
89
     * @inheritDoc
90
     */
91 22
    public function init()
92
    {
93 22
        parent::init();
94 22
        $this->_requestId = \Yii::$app->security->generateRandomString(128);
95
    }
96
97
    /**
98
     * @inheritDoc
99
     */
100 3
    public function rules()
101
    {
102
        return [
103 3
            [['selectedScopeIdentifiers'], 'each', 'rule' => ['string']],
104
            [['authorizationStatus'], 'required'],
105 3
            [['authorizationStatus'], 'in', 'range' => [static::AUTHORIZATION_APPROVED, static::AUTHORIZATION_DENIED]],
106
        ];
107
    }
108
109
    /**
110
     * @inheritDoc
111
     */
112 6
    public function getRequestId()
113
    {
114 6
        return $this->_requestId;
115
    }
116
117
    /**
118
     * @inheritDoc
119
     */
120 1
    public function getState()
121
    {
122 1
        return $this->_state;
123
    }
124
125
    /**
126
     * @inheritDoc
127
     */
128 4
    public function setState($state)
129
    {
130 4
        $this->_state = $state;
131 4
        return $this;
132
    }
133
134
    /**
135
     * @inheritDoc
136
     */
137 10
    public function setClientIdentifier($clientIdentifier)
138
    {
139 10
        if ($this->_client && $this->_client->getIdentifier() !== $clientIdentifier) {
140 3
            $this->_client = null;
141
        }
142
143 10
        $this->_scopeAuthorizationRequests = null;
144 10
        $this->_scopesAppliedByDefaultAutomatically = null;
145
146 10
        return parent::setClientIdentifier($clientIdentifier);
147
    }
148
149
    /**
150
     * @inheritDoc
151
     */
152 11
    public function getClient()
153
    {
154 11
        $clientIdentifier = $this->getClientIdentifier();
155 11
        if (empty($clientIdentifier)) {
156 1
            throw new InvalidCallException('Client identifier must be set.');
157
        }
158 10
        if (empty($this->_client) || $this->_client->getIdentifier() != $clientIdentifier) {
159 9
            $this->_client = $this->getModule()->getClientRepository()->getClientEntity($clientIdentifier);
160
        }
161
162 10
        return $this->_client;
163
    }
164
165
    /**
166
     * @inheritDoc
167
     */
168 2
    public function setClient($client)
169
    {
170 2
        $this->_client = $client;
171 2
        $this->setClientIdentifier($client->getIdentifier());
172 2
        return $this;
173
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178 9
    public function setUserIdentity($userIdentity)
179
    {
180 9
        if ($this->getUserIdentifier() !== $userIdentity->getIdentifier()) {
181 9
            $this->_scopeAuthorizationRequests = null;
182 9
            $this->_scopesAppliedByDefaultAutomatically = null;
183
        }
184
185 9
        return parent::setUserIdentity($userIdentity);
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191 4
    public function setUserAuthenticatedBeforeRequest($authenticatedBeforeRequest)
192
    {
193 4
        $this->_userAuthenticatedBeforeRequest = $authenticatedBeforeRequest;
194 4
        return $this;
195
    }
196
197
    /**
198
     * @inheritDoc
199
     */
200 1
    public function wasUserAuthenticatedBeforeRequest()
201
    {
202 1
        return $this->_userAuthenticatedBeforeRequest;
203
    }
204
205
    /**
206
     * @inheritDoc
207
     */
208 1
    public function setUserAuthenticatedDuringRequest($authenticatedDuringRequest)
209
    {
210 1
        $this->_authenticatedDuringRequest = $authenticatedDuringRequest;
211 1
        return $this;
212
    }
213
214
    /**
215
     * @inheritDoc
216
     */
217 4
    public function wasUserAthenticatedDuringRequest()
218
    {
219 4
        return $this->_authenticatedDuringRequest;
220
    }
221
222
    /**
223
     * @inheritDoc
224
     */
225 7
    public function setRequestedScopeIdentifiers($requestedScopeIdentifiers)
226
    {
227 7
        $this->_scopeAuthorizationRequests = null;
228 7
        $this->_scopesAppliedByDefaultAutomatically = null;
229 7
        return parent::setRequestedScopeIdentifiers($requestedScopeIdentifiers);
230
    }
231
232
    /**
233
     * @inheritDoc
234
     */
235 5
    public function isClientIdentifiable()
236
    {
237
        return
238 5
            $this->getClient()->isConfidential()
239 5
            || StringHelper::startsWith((string)$this->getRedirectUri(), 'https://');
240
    }
241
242
    /**
243
     * @inheritDoc
244
     */
245 4
    public function isAuthorizationNeeded()
246
    {
247
        // Prevent Client Impersonation (https://datatracker.ietf.org/doc/html/rfc6749#section-10.2).
248 4
        if (!$this->isClientIdentifiable()) {
249 2
            return true; // Always require authorization of non-identifiable clients.
250
        }
251
252 3
        $isScopeAuthorizationNeeded = $this->isScopeAuthorizationNeeded();
253
254 3
        return ($this->isClientAuthorizationNeeded() && !$this->getClient()->skipAuthorizationIfScopeIsAllowed())
255
            || $isScopeAuthorizationNeeded;
256
    }
257
258
    /**
259
     * @inheritDoc
260
     */
261 3
    public function isClientAuthorizationNeeded()
262
    {
263
        /** @var Oauth2UserClientInterface $userClientClass */
264 3
        $userClientClass = DiHelper::getValidatedClassName(Oauth2UserClientInterface::class);
265
266 3
        return !$userClientClass::find()
267 3
            ->andWhere([
268 3
                'user_id' => $this->getUserIdentifier(),
269 3
                'client_id' => $this->getClient()->getPrimaryKey(),
270
                'enabled' => 1,
271
            ])
272 3
            ->exists();
273
    }
274
275
    /**
276
     * @inheritDoc
277
     */
278 3
    public function isScopeAuthorizationNeeded()
279
    {
280 3
        foreach ($this->getScopeAuthorizationRequests() as $scopeAuthorizationRequest) {
281 3
            if (!$scopeAuthorizationRequest->getIsAccepted()) {
282 3
                return true;
283
            }
284
        }
285 1
        return false;
286
    }
287
288
    /**
289
     * Get all the Scope Authorization Requests for this Client Authorization Request.
290
     * @return Oauth2ScopeAuthorizationRequestInterface[]
291
     * @throws \yii\base\InvalidConfigException
292
     * @see determineScopeAuthorization()
293
     * @since 1.0.0
294
     */
295 6
    protected function getScopeAuthorizationRequests()
296
    {
297 6
        if ($this->_scopeAuthorizationRequests === null) {
298 6
            $this->determineScopeAuthorization();
299
        }
300
301 6
        return $this->_scopeAuthorizationRequests;
302
    }
303
304
    /**
305
     * Calculate the Scope Authorization Requests for this Client Authorization Request.
306
     * @throws \yii\base\InvalidConfigException
307
     * @since 1.0.0
308
     */
309 7
    protected function determineScopeAuthorization()
310
    {
311 7
        $scopeAuthorizationRequests = [];
312 7
        $scopesAppliedByDefaultAutomatically = [];
313
314 7
        $client = $this->getClient();
315 7
        $scopes = $client->getAllowedScopes($this->getRequestedScopeIdentifiers());
316
317
        /** @var Oauth2UserClientScopeInterface $userClientScopeClass */
318 7
        $userClientScopeClass = DiHelper::getValidatedClassName(Oauth2UserClientScopeInterface::class);
319
320
        /** @var Oauth2UserClientScopeInterface[] $userClientScopes */
321 7
        $userClientScopes = $userClientScopeClass::find()
322 7
            ->andWhere([
323 7
                'user_id' => $this->getUserIdentity()->getIdentifier(),
324 7
                'client_id' => $client->getPrimaryKey(),
325 7
                'scope_id' => array_map(fn($scope) => $scope->getPrimaryKey(), $scopes)
326
            ])
327 7
            ->indexBy('scope_id')
328 7
            ->all();
329
330 7
        foreach ($scopes as $scope) {
331 6
            $clientScope = $scope->getClientScope($client->getPrimaryKey());
332 6
            $appliedByDefault = ($clientScope ? $clientScope->getAppliedByDefault() : null)
333 6
                ?? $scope->getAppliedByDefault();
334
335 6
            $scopeIdentifier = $scope->getIdentifier();
336 6
            if ($appliedByDefault === Oauth2Scope::APPLIED_BY_DEFAULT_AUTOMATICALLY) {
337 6
                unset($scopeAuthorizationRequests[$scopeIdentifier]);
338 6
                $scopesAppliedByDefaultAutomatically[$scopeIdentifier] = $scope;
339
            } else {
340 6
                $isRequired = ($clientScope ? $clientScope->getRequiredOnAuthorization() : null)
341 6
                    ?? $scope->getRequiredOnAuthorization();
342 6
                $userClientScope = $userClientScopes[$scope->getPrimaryKey()] ?? null;
343
344
                /** @var Oauth2ScopeAuthorizationRequestInterface $scopeAuthorizationRequest */
345 6
                $scopeAuthorizationRequest = Yii::createObject(Oauth2ScopeAuthorizationRequestInterface::class);
346
                $scopeAuthorizationRequest
347 6
                    ->setScope($scope)
348 6
                    ->setIsRequired($isRequired)
0 ignored issues
show
Bug introduced by
It seems like $isRequired can also be of type null; however, parameter $isRequired of rhertogh\Yii2Oauth2Serve...erface::setIsRequired() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

348
                    ->setIsRequired(/** @scrutinizer ignore-type */ $isRequired)
Loading history...
349 6
                    ->setIsAccepted($userClientScope && $userClientScope->isEnabled())
350 6
                    ->setHasBeenRejectedBefore($userClientScope && !$userClientScope->isEnabled());
351
352 6
                $scopeAuthorizationRequests[$scopeIdentifier] = $scopeAuthorizationRequest;
353
            }
354
        }
355
356 7
        $this->_scopeAuthorizationRequests = $scopeAuthorizationRequests;
357 7
        $this->_scopesAppliedByDefaultAutomatically = $scopesAppliedByDefaultAutomatically;
358
    }
359
360
    /**
361
     * @inheritDoc
362
     */
363 4
    public function getApprovalPendingScopes()
364
    {
365 4
        $pendingApprovalRequests = [];
366 4
        foreach ($this->getScopeAuthorizationRequests() as $scopeIdentifier => $scopeAuthorizationRequest) {
367 3
            if (!$scopeAuthorizationRequest->getIsAccepted()) {
368 3
                $pendingApprovalRequests[$scopeIdentifier] = $scopeAuthorizationRequest;
369
            }
370
        }
371 4
        return $pendingApprovalRequests;
372
    }
373
374
    /**
375
     * @inheritDoc
376
     */
377 4
    public function getPreviouslyApprovedScopes()
378
    {
379 4
        $previouslyApprovedScopes = [];
380 4
        foreach ($this->getScopeAuthorizationRequests() as $scopeIdentifier => $scopeAuthorizationRequest) {
381 3
            if ($scopeAuthorizationRequest->getIsAccepted()) {
382 2
                $previouslyApprovedScopes[$scopeIdentifier] = $scopeAuthorizationRequest;
383
            }
384
        }
385 4
        return $previouslyApprovedScopes;
386
    }
387
388
    /**
389
     * @inheritDoc
390
     */
391 1
    public function getScopesAppliedByDefaultAutomatically()
392
    {
393 1
        if ($this->_scopesAppliedByDefaultAutomatically === null) {
394 1
            $this->determineScopeAuthorization();
395
        }
396
397 1
        return $this->_scopesAppliedByDefaultAutomatically;
398
    }
399
400
    /**
401
     * @inheritdoc
402
     */
403 3
    public function isAuthorizationAllowed()
404
    {
405
        return
406 3
            ($this->getClient() !== null)
407 3
            && ($this->getGrantType() !== null)
408
            && (
409 3
                $this->getClient()->endUsersMayAuthorizeClient()
410 3
                && $this->getUserIdentity() !== null
411
            );
412
    }
413
414
    /**
415
     * @inheritDoc
416
     */
417 3
    public function processAuthorization()
418
    {
419 3
        if ($this->getAuthorizationStatus() === null) {
420 1
            throw new InvalidCallException('Unable to process authorization without authorization status.');
421
        }
422
423 2
        $userId = $this->getUserIdentifier();
424 2
        $clientId = $this->getClient()->getPrimaryKey();
425
426
        /** @var Oauth2UserClientInterface $userClientClass */
427 2
        $userClientClass = DiHelper::getValidatedClassName(Oauth2UserClientInterface::class);
428
429 2
        $userClient = $userClientClass::findOrCreate([
430
            'user_id' => $userId,
431
            'client_id' => $clientId,
432
        ]);
433
434 2
        if ($userClient->getIsNewRecord()) {
435 1
            if ($this->isApproved()) {
436 1
                $userClient->setEnabled(true);
437 1
                $userClient->persist();
438
            }
439 1
        } elseif ($userClient->isEnabled() != $this->isApproved()) {
440 1
            $userClient->setEnabled($this->isApproved());
441 1
            $userClient->persist();
442
        }
443
444 2
        if ($this->isApproved()) {
445
446
            /** @var Oauth2UserClientScopeInterface $userClientScopeClass */
447 1
            $userClientScopeClass = DiHelper::getValidatedClassName(Oauth2UserClientScopeInterface::class);
448
449
            /** @var Oauth2UserClientScopeInterface[] $userClientScopes */
450 1
            $userClientScopes = $userClientScopeClass::find()
451 1
                ->andWhere([
452
                    'user_id' => $userId,
453
                    'client_id' => $clientId,
454
                ])
455 1
                ->indexBy('scope_id')
456 1
                ->all();
457
458
459 1
            $scopeAuthorizationRequests = $this->getScopeAuthorizationRequests();
460 1
            $selectedScopeIdentifiers = $this->getSelectedScopeIdentifiers();
461
462
            $scopeAcceptance = [
463 1
                0 => [],
464
                1 => [],
465
            ];
466 1
            foreach ($scopeAuthorizationRequests as $scopeAuthorizationRequest) {
467 1
                $scope = $scopeAuthorizationRequest->getScope();
468 1
                $isAccepted = in_array($scope->getIdentifier(), $selectedScopeIdentifiers);
469 1
                $scopeAcceptance[$isAccepted][] = $scope;
470
            }
471
472 1
            foreach ($scopeAcceptance as $isAccepted => $scopes) {
473 1
                foreach ($scopes as $scope) {
474 1
                    $scopeId = $scope->getPrimaryKey();
475
                    /** @var Oauth2UserClientScopeInterface $userClientScope */
476 1
                    $userClientScope = $userClientScopes[$scopeId] ?? Yii::createObject([
477
                            'class' => Oauth2UserClientScopeInterface::class,
478
                            'user_id' => $userId,
479
                            'client_id' => $clientId,
480
                            'scope_id' => $scopeId,
481
                        ]);
482 1
                    $userClientScope->setEnabled($isAccepted);
0 ignored issues
show
Bug introduced by
$isAccepted of type integer is incompatible with the type boolean expected by parameter $isEnabled of rhertogh\Yii2Oauth2Serve...Interface::setEnabled(). ( Ignorable by Annotation )

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

482
                    $userClientScope->setEnabled(/** @scrutinizer ignore-type */ $isAccepted);
Loading history...
483 1
                    $userClientScope->persist();
484
                }
485
            }
486
        }
487
488 2
        $this->setCompleted(true);
489
    }
490
491
    /**
492
     * @inheritDoc
493
     */
494 1
    public function getAuthorizationRequestUrl()
495
    {
496 1
        return UrlHelper::addQueryParams(
497 1
            $this->getAuthorizeUrl(),
498
            [
499 1
                'clientAuthorizationRequestId' => $this->getRequestId()
500
            ]
501
        );
502
    }
503
}
504