Passed
Push — master ( fb60c6...a721e7 )
by Rutger
03:14
created

setEndUserAuthorizationRequired()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace rhertogh\Yii2Oauth2Server\components\authorization\EndSession\base;
4
5
use Lcobucci\JWT\Encoding\JoseEncoder;
6
use Lcobucci\JWT\Signer\Key\InMemory;
7
use Lcobucci\JWT\Signer\Rsa\Sha256;
8
use Lcobucci\JWT\Token\Parser;
9
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
10
use Lcobucci\JWT\Validation\Constraint\RelatedTo;
11
use Lcobucci\JWT\Validation\Constraint\SignedWith;
12
use Lcobucci\JWT\Validation\Validator;
13
use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator;
14
use rhertogh\Yii2Oauth2Server\components\authorization\base\Oauth2BaseAuthorizationRequest;
15
use rhertogh\Yii2Oauth2Server\helpers\UrlHelper;
16
use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\EndSession\Oauth2EndSessionAuthorizationRequestInterface;
17
use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface;
18
use yii\base\InvalidCallException;
19
use yii\web\BadRequestHttpException;
20
use yii\web\ForbiddenHttpException;
21
use yii\web\UnauthorizedHttpException;
22
23
abstract class Oauth2BaseEndSessionAuthorizationRequest extends Oauth2BaseAuthorizationRequest implements Oauth2EndSessionAuthorizationRequestInterface
24
{
25
    /**
26
     * @var string|null
27
     */
28
    protected $_idTokenHint;
29
30
    /**
31
     * @var string|null
32
     */
33
    protected $_endSessionUrl;
34
35
    /**
36
     * @var bool
37
     */
38
    protected $_validatedRequest = false;
39
40
    /**
41
     * @var bool|null
42
     */
43
    protected $_endUserAuthorizationRequired = null;
44
45
    public function __serialize()
46
    {
47
        return array_merge(parent::__serialize(), [
48
            '_idTokenHint' => $this->_idTokenHint,
49
            '_endSessionUrl' => $this->_endSessionUrl,
50
        ]);
51
    }
52
53
    /**
54
     * @inheritDoc
55
     */
56
    public function getIdTokenHint()
57
    {
58
        return $this->_idTokenHint;
59
    }
60
61
    /**
62
     * @inheritDoc
63
     */
64
    public function setIdTokenHint($idTokenHint)
65
    {
66
        $this->_idTokenHint = $idTokenHint;
67
        $this->_validatedRequest = false;
68
        return $this;
69
    }
70
71
    public function setClientIdentifier($clientIdentifier)
72
    {
73
        $this->_validatedRequest = false;
74
        return parent::setClientIdentifier($clientIdentifier);
75
    }
76
77
    /**
78
     * @inheritDoc
79
     */
80
    public function getEndSessionUrl()
81
    {
82
        return $this->_endSessionUrl;
83
    }
84
85
    /**
86
     * @inheritDoc
87
     */
88
    public function setEndSessionUrl($endSessionUrl)
89
    {
90
        $this->_endSessionUrl = $endSessionUrl;
91
        return $this;
92
    }
93
94
    public function validateRequest()
95
    {
96
        $module = $this->getModule();
97
        $idTokenHint = $this->getIdTokenHint();
98
        $clientIdentifier = $this->getClientIdentifier();
99
        $identity = $module->getUserIdentity();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $identity is correct as $module->getUserIdentity() targeting rhertogh\Yii2Oauth2Serve...dule::getUserIdentity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
100
        $postLogoutRedirectUri = $this->getRedirectUri();
101
102
        $endUserConfirmationMayBeSkipped = false;
103
104
        if ($idTokenHint) {
105
106
            $parser = new Parser(new JoseEncoder());
107
108
            $idToken = $parser->parse($idTokenHint);
109
110
            $validator = new Validator();
111
112
            if (!$validator->validate($idToken, new SignedWith(
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
113
                new Sha256(),
114
                InMemory::plainText($module->getPublicKey()->getKeyContents())
115
            ))) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line control structure must be indented at least once; expected at least 16 spaces, but found 12
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
116
                throw new UnauthorizedHttpException('Invalid `id_token_hint` signature.');
117
            }
118
119
            if ($clientIdentifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clientIdentifier of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
120
                if (!$validator->validate($idToken, new PermittedFor($clientIdentifier))) {
121
                    throw new UnauthorizedHttpException('Invalid "aud" claim in `id_token_hint`.');
122
                }
123
            } else {
124
                $audiences = $idToken->claims()->get('aud');
125
                if (count($audiences) === 1) {
0 ignored issues
show
Bug introduced by
It seems like $audiences can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, 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

125
                if (count(/** @scrutinizer ignore-type */ $audiences) === 1) {
Loading history...
126
                    $clientIdentifier = $audiences[0];
127
                    $this->setClientIdentifier($clientIdentifier);
128
                } else {
129
                    throw new BadRequestHttpException(
130
                        'The `client_id` parameter is required when there are multiple audiences'
131
                        . ' in the "aud" claim of the `id_token_hint`.'
132
                    );
133
                }
134
            }
135
136
            if ($identity) {
0 ignored issues
show
introduced by
$identity is of type null, thus it always evaluated to false.
Loading history...
137
                if ($validator->validate($idToken, new RelatedTo((string)$identity->getIdentifier()))) {
138
                    $endUserConfirmationMayBeSkipped = true;
139
                }
140
            }
141
        } else {
142
            if (!$module->openIdConnectAllowAnonymousRpInitiatedLogout) {
143
                throw new BadRequestHttpException('The `id_token_hint` parameter is required.');
144
            }
145
        }
146
147
        if ($clientIdentifier) {
148
            $client = $module->getClientRepository()->getClientEntity($clientIdentifier);
149
            if (!$client || !$client->isEnabled()) {
150
                throw new ForbiddenHttpException('Client "' . $clientIdentifier . '" not found or disabled.');
151
            }
152
153
            if (
154
                !($client->getOpenIdConnectRpInitiatedLogout()
155
                    > Oauth2ClientInterface::OIDC_RP_INITIATED_LOGOUT_DISABLED)
156
            ) {
157
                throw new ForbiddenHttpException('Client "' . $clientIdentifier . '" is not allowed to initiated end-user logout.');
158
            }
159
        }
160
161
        if ($postLogoutRedirectUri) {
162
            if (!empty($client)) {
163
                $allowedPostLogoutRedirectUris = $client->getPostLogoutRedirectUris();
164
                $validatePostLogoutRedirectUri = $postLogoutRedirectUri;
165
                if ($client->isVariableRedirectUriQueryAllowed()) {
166
                    $validatePostLogoutRedirectUri = UrlHelper::stripQueryAndFragment($validatePostLogoutRedirectUri);
167
                }
168
169
                $validator = new RedirectUriValidator($allowedPostLogoutRedirectUris);
170
                if (!$validator->validateRedirectUri($validatePostLogoutRedirectUri)) {
171
                    throw new UnauthorizedHttpException('Invalid `post_logout_redirect_uri`.');
172
                }
173
            } else {
174
                throw new UnauthorizedHttpException('`post_logout_redirect_uri` is only allowed if the client is known.');
175
            }
176
        }
177
178
        $endUserAuthorizationRequired = !(
179
            $endUserConfirmationMayBeSkipped
0 ignored issues
show
introduced by
The condition $endUserConfirmationMayBeSkipped is always false.
Loading history...
180
            && isset($client)
181
            && (
182
                $client->getOpenIdConnectRpInitiatedLogout()
183
                    === Oauth2ClientInterface::OIDC_RP_INITIATED_LOGOUT_ENABLED_WITHOUT_CONFIRMATION
184
            )
185
        );
186
187
        $this->setEndUserAuthorizationRequired($endUserAuthorizationRequired);
188
        $this->_validatedRequest = true;
189
    }
190
191
    public function getEndUserAuthorizationRequired()
192
    {
193
        if (!$this->_validatedRequest) {
194
            throw new InvalidCallException('Request must be validated first');
195
        }
196
197
        return $this->_endUserAuthorizationRequired;
198
    }
199
200
    public function setEndUserAuthorizationRequired($endUserAuthorizationRequired)
201
    {
202
        $this->_endUserAuthorizationRequired = $endUserAuthorizationRequired;
203
        return $this;
204
    }
205
206
}
207