Passed
Push — master ( 8a0881...eebf27 )
by Rutger
03:05
created

isAuthorizationNeeded()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 11
ccs 5
cts 5
cp 1
rs 10
cc 4
nc 4
nop 0
crap 4
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 $_scopesAppliedByDefaultWithoutConfirm = 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 25
    public function init()
92
    {
93 25
        parent::init();
94 25
        $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 13
    public function setClientIdentifier($clientIdentifier)
138
    {
139 13
        if ($this->_client && $this->_client->getIdentifier() !== $clientIdentifier) {
140 3
            $this->_client = null;
141
        }
142
143 13
        $this->_scopeAuthorizationRequests = null;
144 13
        $this->_scopesAppliedByDefaultWithoutConfirm = null;
145
146 13
        return parent::setClientIdentifier($clientIdentifier);
147
    }
148
149
    /**
150
     * @inheritDoc
151
     */
152 14
    public function getClient()
153
    {
154 14
        $clientIdentifier = $this->getClientIdentifier();
155 14
        if (empty($clientIdentifier)) {
156 1
            throw new InvalidCallException('Client identifier must be set.');
157
        }
158 13
        if (empty($this->_client) || $this->_client->getIdentifier() != $clientIdentifier) {
159 9
            $this->_client = $this->getModule()->getClientRepository()->getClientEntity($clientIdentifier);
160
        }
161
162 13
        return $this->_client;
163
    }
164
165
    /**
166
     * @inheritDoc
167
     */
168 5
    public function setClient($client)
169
    {
170 5
        $this->_client = $client;
171 5
        $this->setClientIdentifier($client->getIdentifier());
172 5
        return $this;
173
    }
174
175
    /**
176
     * @inheritDoc
177
     */
178 12
    public function setUserIdentity($userIdentity)
179
    {
180 12
        if ($this->getUserIdentifier() !== $userIdentity->getIdentifier()) {
181 12
            $this->_scopeAuthorizationRequests = null;
182 12
            $this->_scopesAppliedByDefaultWithoutConfirm = null;
183
        }
184
185 12
        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 11
    public function setRequestedScopeIdentifiers($requestedScopeIdentifiers)
226
    {
227 11
        $this->_scopeAuthorizationRequests = null;
228 11
        $this->_scopesAppliedByDefaultWithoutConfirm = null;
229 11
        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 10
    protected function determineScopeAuthorization()
310
    {
311 10
        $scopeAuthorizationRequests = [];
312 10
        $scopesAppliedByDefaultWithoutConfirm = [];
313
314 10
        $client = $this->getClient();
315 10
        $requestedScopeIdentifiers = $this->getRequestedScopeIdentifiers();
316 10
        $allowedScopes = $client->getAllowedScopes($requestedScopeIdentifiers);
317
318
        /** @var Oauth2UserClientScopeInterface $userClientScopeClass */
319 10
        $userClientScopeClass = DiHelper::getValidatedClassName(Oauth2UserClientScopeInterface::class);
320
321
        /** @var Oauth2UserClientScopeInterface[] $userClientScopes */
322 10
        $userClientScopes = $userClientScopeClass::find()
323 10
            ->andWhere([
324 10
                'user_id' => $this->getUserIdentity()->getIdentifier(),
325 10
                'client_id' => $client->getPrimaryKey(),
326 10
                'scope_id' => array_map(fn($scope) => $scope->getPrimaryKey(), $allowedScopes)
327
            ])
328 10
            ->indexBy('scope_id')
329 10
            ->all();
330
331 10
        foreach ($allowedScopes as $scope) {
332 9
            $clientScope = $scope->getClientScope($client->getPrimaryKey());
333 9
            $appliedByDefault = ($clientScope ? $clientScope->getAppliedByDefault() : null)
334 9
                ?? $scope->getAppliedByDefault();
335
336 9
            $scopeIdentifier = $scope->getIdentifier();
337
            if (
338
                ($appliedByDefault === Oauth2ScopeInterface::APPLIED_BY_DEFAULT_AUTOMATICALLY)
339
                || (
340
                    $appliedByDefault === Oauth2ScopeInterface::APPLIED_BY_DEFAULT_IF_REQUESTED
341 9
                    && in_array($scopeIdentifier, $requestedScopeIdentifiers)
342
                )
343
            ) {
344 9
                unset($scopeAuthorizationRequests[$scopeIdentifier]);
345 9
                $scopesAppliedByDefaultWithoutConfirm[$scopeIdentifier] = $scope;
346 9
            } elseif ($appliedByDefault !== Oauth2ScopeInterface::APPLIED_BY_DEFAULT_IF_REQUESTED) {
347 9
                $isRequired = ($clientScope ? $clientScope->getRequiredOnAuthorization() : null)
348 9
                    ?? $scope->getRequiredOnAuthorization();
349 9
                $userClientScope = $userClientScopes[$scope->getPrimaryKey()] ?? null;
350
351
                /** @var Oauth2ScopeAuthorizationRequestInterface $scopeAuthorizationRequest */
352 9
                $scopeAuthorizationRequest = Yii::createObject(Oauth2ScopeAuthorizationRequestInterface::class);
353
                $scopeAuthorizationRequest
354 9
                    ->setScope($scope)
355 9
                    ->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

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

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