isAuthorizationAllowed()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 8
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 5
nop 0
crap 4
1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\components\authorization\client;
4
5
use rhertogh\Yii2Oauth2Server\components\authorization\client\base\Oauth2BaseClientAuthorizationRequest;
6
use rhertogh\Yii2Oauth2Server\helpers\DiHelper;
7
use rhertogh\Yii2Oauth2Server\helpers\UrlHelper;
8
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\client\Oauth2ClientScopeAuthorizationRequestInterface;
9
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ScopeInterface;
10
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2UserClientInterface;
11
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2UserClientScopeInterface;
12
use Yii;
13
use yii\base\InvalidCallException;
14
15
class Oauth2ClientAuthorizationRequest extends Oauth2BaseClientAuthorizationRequest
16
{
17
    /**
18
     * Internal caching
19
     * @var Oauth2ClientScopeAuthorizationRequestInterface[]|null
20
     */
21
    protected $_scopeAuthorizationRequests = null;
22
23
    /**
24
     * Internal caching
25
     * @var Oauth2ScopeInterface[]|null
26
     */
27
    protected $_scopesAppliedByDefaultWithoutConfirm = null;
28
29
    /**
30
     * @inheritDoc
31
     */
32 3
    public function rules()
33
    {
34 3
        return array_merge(parent::rules(), [
35 3
            [['selectedScopeIdentifiers'], 'each', 'rule' => ['string']],
36 3
        ]);
37
    }
38
39
    /**
40
     * @inheritDoc
41
     */
42 13
    public function setClientIdentifier($clientIdentifier)
43
    {
44 13
        $this->_scopeAuthorizationRequests = null;
45 13
        $this->_scopesAppliedByDefaultWithoutConfirm = null;
46
47 13
        return parent::setClientIdentifier($clientIdentifier);
48
    }
49
50
    /**
51
     * @inheritDoc
52
     */
53 12
    public function setUserIdentity($userIdentity)
54
    {
55 12
        if ($this->getUserIdentifier() !== $userIdentity->getIdentifier()) {
56 12
            $this->_scopeAuthorizationRequests = null;
57 12
            $this->_scopesAppliedByDefaultWithoutConfirm = null;
58
        }
59
60 12
        return parent::setUserIdentity($userIdentity);
61
    }
62
63
    /**
64
     * @inheritDoc
65
     */
66 11
    public function setRequestedScopeIdentifiers($requestedScopeIdentifiers)
67
    {
68 11
        $this->_scopeAuthorizationRequests = null;
69 11
        $this->_scopesAppliedByDefaultWithoutConfirm = null;
70 11
        return parent::setRequestedScopeIdentifiers($requestedScopeIdentifiers);
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76 4
    public function isAuthorizationNeeded()
77
    {
78
        // Prevent Client Impersonation (https://datatracker.ietf.org/doc/html/rfc6749#section-10.2).
79 4
        if (!$this->isClientIdentifiable()) {
80 2
            return true; // Always require authorization of non-identifiable clients.
81
        }
82
83 3
        $isScopeAuthorizationNeeded = $this->isScopeAuthorizationNeeded();
84
85 3
        return ($this->isClientAuthorizationNeeded() && !$this->getClient()->skipAuthorizationIfScopeIsAllowed())
86 3
            || $isScopeAuthorizationNeeded;
87
    }
88
89
    /**
90
     * @inheritDoc
91
     */
92 3
    public function isClientAuthorizationNeeded()
93
    {
94
        /** @var Oauth2UserClientInterface $userClientClass */
95 3
        $userClientClass = DiHelper::getValidatedClassName(Oauth2UserClientInterface::class);
96
97 3
        return !$userClientClass::find()
98 3
            ->andWhere([
99 3
                'user_id' => $this->getUserIdentifier(),
100 3
                'client_id' => $this->getClient()->getPrimaryKey(),
101 3
                'enabled' => 1,
102 3
            ])
103 3
            ->exists();
104
    }
105
106
    /**
107
     * @inheritDoc
108
     */
109 3
    public function isScopeAuthorizationNeeded()
110
    {
111 3
        foreach ($this->getScopeAuthorizationRequests() as $scopeAuthorizationRequest) {
112 3
            if (!$scopeAuthorizationRequest->getIsAccepted()) {
113 3
                return true;
114
            }
115
        }
116 1
        return false;
117
    }
118
119
    /**
120
     * Get all the Scope Authorization Requests for this Client Authorization Request.
121
     * @return Oauth2ClientScopeAuthorizationRequestInterface[]
122
     * @throws \yii\base\InvalidConfigException
123
     * @see determineScopeAuthorization()
124
     * @since 1.0.0
125
     */
126 6
    protected function getScopeAuthorizationRequests()
127
    {
128 6
        if ($this->_scopeAuthorizationRequests === null) {
129 6
            $this->determineScopeAuthorization();
130
        }
131
132 6
        return $this->_scopeAuthorizationRequests;
133
    }
134
135
    /**
136
     * Calculate the Scope Authorization Requests for this Client Authorization Request.
137
     * @throws \yii\base\InvalidConfigException
138
     * @since 1.0.0
139
     */
140 10
    protected function determineScopeAuthorization()
141
    {
142 10
        $scopeAuthorizationRequests = [];
143 10
        $scopesAppliedByDefaultWithoutConfirm = [];
144
145 10
        $client = $this->getClient();
146 10
        $requestedScopeIdentifiers = $this->getRequestedScopeIdentifiers();
147 10
        $allowedScopes = $client->getAllowedScopes($requestedScopeIdentifiers);
148
149
        /** @var Oauth2UserClientScopeInterface $userClientScopeClass */
150 10
        $userClientScopeClass = DiHelper::getValidatedClassName(Oauth2UserClientScopeInterface::class);
151
152
        /** @var Oauth2UserClientScopeInterface[] $userClientScopes */
153 10
        $userClientScopes = $userClientScopeClass::find()
154 10
            ->andWhere([
155 10
                'user_id' => $this->getUserIdentity()->getIdentifier(),
156 10
                'client_id' => $client->getPrimaryKey(),
157 10
                'scope_id' => array_map(fn($scope) => $scope->getPrimaryKey(), $allowedScopes)
158 10
            ])
159 10
            ->indexBy('scope_id')
160 10
            ->all();
161
162 10
        foreach ($allowedScopes as $scope) {
163 9
            $clientScope = $scope->getClientScope($client->getPrimaryKey());
164 9
            $appliedByDefault = ($clientScope ? $clientScope->getAppliedByDefault() : null)
165 9
                ?? $scope->getAppliedByDefault();
166
167 9
            $scopeIdentifier = $scope->getIdentifier();
168
            if (
169 9
                ($appliedByDefault === Oauth2ScopeInterface::APPLIED_BY_DEFAULT_AUTOMATICALLY)
170
                || (
171 9
                    $appliedByDefault === Oauth2ScopeInterface::APPLIED_BY_DEFAULT_IF_REQUESTED
172 9
                    && in_array($scopeIdentifier, $requestedScopeIdentifiers)
173
                )
174
            ) {
175 9
                unset($scopeAuthorizationRequests[$scopeIdentifier]);
176 9
                $scopesAppliedByDefaultWithoutConfirm[$scopeIdentifier] = $scope;
177 9
            } elseif ($appliedByDefault !== Oauth2ScopeInterface::APPLIED_BY_DEFAULT_IF_REQUESTED) {
178 9
                $isRequired = ($clientScope ? $clientScope->getRequiredOnAuthorization() : null)
179 9
                    ?? $scope->getRequiredOnAuthorization();
180 9
                $userClientScope = $userClientScopes[$scope->getPrimaryKey()] ?? null;
181
182
                /** @var Oauth2ClientScopeAuthorizationRequestInterface $scopeAuthorizationRequest */
183 9
                $scopeAuthorizationRequest = Yii::createObject(Oauth2ClientScopeAuthorizationRequestInterface::class);
184 9
                $scopeAuthorizationRequest
185 9
                    ->setScope($scope)
186 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

186
                    ->setIsRequired(/** @scrutinizer ignore-type */ $isRequired)
Loading history...
187 9
                    ->setIsAccepted($userClientScope && $userClientScope->isEnabled())
188 9
                    ->setHasBeenRejectedBefore($userClientScope && !$userClientScope->isEnabled());
189
190 9
                $scopeAuthorizationRequests[$scopeIdentifier] = $scopeAuthorizationRequest;
191
            }
192
        }
193
194 10
        $this->_scopeAuthorizationRequests = $scopeAuthorizationRequests;
195 10
        $this->_scopesAppliedByDefaultWithoutConfirm = $scopesAppliedByDefaultWithoutConfirm;
196
    }
197
198
    /**
199
     * @inheritDoc
200
     */
201 4
    public function getApprovalPendingScopes()
202
    {
203 4
        $pendingApprovalRequests = [];
204 4
        foreach ($this->getScopeAuthorizationRequests() as $scopeIdentifier => $scopeAuthorizationRequest) {
205 3
            if (!$scopeAuthorizationRequest->getIsAccepted()) {
206 3
                $pendingApprovalRequests[$scopeIdentifier] = $scopeAuthorizationRequest;
207
            }
208
        }
209 4
        return $pendingApprovalRequests;
210
    }
211
212
    /**
213
     * @inheritDoc
214
     */
215 4
    public function getPreviouslyApprovedScopes()
216
    {
217 4
        $previouslyApprovedScopes = [];
218 4
        foreach ($this->getScopeAuthorizationRequests() as $scopeIdentifier => $scopeAuthorizationRequest) {
219 3
            if ($scopeAuthorizationRequest->getIsAccepted()) {
220 2
                $previouslyApprovedScopes[$scopeIdentifier] = $scopeAuthorizationRequest;
221
            }
222
        }
223 4
        return $previouslyApprovedScopes;
224
    }
225
226
    /**
227
     * @inheritDoc
228
     */
229 4
    public function getScopesAppliedByDefaultWithoutConfirm()
230
    {
231 4
        if ($this->_scopesAppliedByDefaultWithoutConfirm === null) {
232 4
            $this->determineScopeAuthorization();
233
        }
234
235 4
        return $this->_scopesAppliedByDefaultWithoutConfirm;
236
    }
237
238
    /**
239
     * @inheritdoc
240
     */
241 3
    public function isAuthorizationAllowed()
242
    {
243 3
        return
244 3
            ($this->getClient() !== null)
245 3
            && ($this->getGrantType() !== null)
246 3
            && (
247 3
                $this->getClient()->endUsersMayAuthorizeClient()
248 3
                && $this->getUserIdentity() !== null
249 3
            );
250
    }
251
252
    /**
253
     * @inheritDoc
254
     */
255 3
    public function processAuthorization()
256
    {
257 3
        if ($this->getAuthorizationStatus() === null) {
258 1
            throw new InvalidCallException('Unable to process authorization without authorization status.');
259
        }
260
261 2
        $userId = $this->getUserIdentifier();
262 2
        $clientId = $this->getClient()->getPrimaryKey();
263
264
        /** @var Oauth2UserClientInterface $userClientClass */
265 2
        $userClientClass = DiHelper::getValidatedClassName(Oauth2UserClientInterface::class);
266
267 2
        $userClient = $userClientClass::findOrCreate([
268 2
            'user_id' => $userId,
269 2
            'client_id' => $clientId,
270 2
        ]);
271
272 2
        if ($userClient->getIsNewRecord()) {
273 1
            if ($this->isApproved()) {
274 1
                $userClient->setEnabled(true);
275 1
                $userClient->persist();
276
            }
277 1
        } elseif ($userClient->isEnabled() != $this->isApproved()) {
278 1
            $userClient->setEnabled($this->isApproved());
279 1
            $userClient->persist();
280
        }
281
282 2
        if ($this->isApproved()) {
283
284
            /** @var Oauth2UserClientScopeInterface $userClientScopeClass */
285 1
            $userClientScopeClass = DiHelper::getValidatedClassName(Oauth2UserClientScopeInterface::class);
286
287
            /** @var Oauth2UserClientScopeInterface[] $userClientScopes */
288 1
            $userClientScopes = $userClientScopeClass::find()
289 1
                ->andWhere([
290 1
                    'user_id' => $userId,
291 1
                    'client_id' => $clientId,
292 1
                ])
293 1
                ->indexBy('scope_id')
294 1
                ->all();
295
296
297 1
            $scopeAuthorizationRequests = $this->getScopeAuthorizationRequests();
298 1
            $selectedScopeIdentifiers = $this->getSelectedScopeIdentifiers();
299
300 1
            $scopeAcceptance = [
301 1
                0 => [],
302 1
                1 => [],
303 1
            ];
304 1
            foreach ($scopeAuthorizationRequests as $scopeAuthorizationRequest) {
305 1
                $scope = $scopeAuthorizationRequest->getScope();
306 1
                $isAccepted = in_array($scope->getIdentifier(), $selectedScopeIdentifiers);
307 1
                $scopeAcceptance[$isAccepted][] = $scope;
308
            }
309
310 1
            foreach ($scopeAcceptance as $isAccepted => $scopes) {
311 1
                foreach ($scopes as $scope) {
312 1
                    $scopeId = $scope->getPrimaryKey();
313
                    /** @var Oauth2UserClientScopeInterface $userClientScope */
314 1
                    $userClientScope = $userClientScopes[$scopeId] ?? Yii::createObject([
315 1
                            'class' => Oauth2UserClientScopeInterface::class,
316 1
                            'user_id' => $userId,
317 1
                            'client_id' => $clientId,
318 1
                            'scope_id' => $scopeId,
319 1
                        ]);
320 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

320
                    $userClientScope->setEnabled(/** @scrutinizer ignore-type */ $isAccepted);
Loading history...
321 1
                    $userClientScope->persist();
322
                }
323
            }
324
        }
325
326 2
        $this->setCompleted(true);
327
    }
328
329
    /**
330
     * @inheritDoc
331
     */
332 1
    public function getAuthorizationRequestUrl()
333
    {
334 1
        return UrlHelper::addQueryParams(
335 1
            $this->getAuthorizeUrl(),
336 1
            [
337 1
                'clientAuthorizationRequestId' => $this->getRequestId()
338 1
            ]
339 1
        );
340
    }
341
}
342