Passed
Push — master ( 06a7ac...ba6e27 )
by Dāvis
11:11
created

OpenIDConnectProvider::getAccessToken()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 74
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nc 9
nop 2
dl 0
loc 74
rs 6.6374
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Sludio\HelperBundle\Openidconnect\Provider;
4
5
use Lcobucci\JWT\Signer;
6
use Lcobucci\JWT\Signer\Key;
7
use Lcobucci\JWT\Signer\Rsa\Sha256;
8
use Lcobucci\JWT\Token;
9
use League\OAuth2\Client\Grant\AbstractGrant;
10
use League\OAuth2\Client\Provider\AbstractProvider;
11
use League\OAuth2\Client\Token\AccessToken as BaseAccessToken;
12
use Psr\Http\Message\ResponseInterface;
13
use Sludio\HelperBundle\Openidconnect\Component\Providerable;
14
use Sludio\HelperBundle\Openidconnect\Security\Exception\InvalidTokenException;
15
use Sludio\HelperBundle\Openidconnect\Specification;
16
use Symfony\Bundle\FrameworkBundle\Routing\Router;
17
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
18
19
class OpenIDConnectProvider extends AbstractProvider implements Providerable
20
{
21
    /**
22
     * @var string
23
     */
24
    protected $publicKey;
25
26
    /**
27
     * @var Signer
28
     */
29
    protected $signer;
30
31
    /**
32
     * @var Specification\ValidatorChain
33
     */
34
    protected $validatorChain;
35
36
    /**
37
     * @var string
38
     */
39
    protected $idTokenIssuer;
40
    /**
41
     * @var Uri[]
42
     */
43
    protected $uris = [];
44
45
    /**
46
     * @var bool
47
     */
48
    protected $useSession;
49
    /**
50
     * @var Router
51
     */
52
    private $router;
53
    /**
54
     * @var string
55
     */
56
    private $baseUri;
57
58
    /**
59
     * @param array  $options
60
     * @param array  $collaborators
61
     * @param Router $router
62
     */
63
    public function __construct(array $options = [], array $collaborators = [], Router $router)
64
    {
65
        $this->signer = new Sha256();
66
67
        $this->validatorChain = new Specification\ValidatorChain();
68
        $this->validatorChain->setValidators([
69
            new Specification\NotEmpty('iat', true),
70
            new Specification\GreaterOrEqualsTo('exp', true),
71
            new Specification\EqualsTo('iss', true),
72
            new Specification\EqualsTo('aud', true),
73
            new Specification\NotEmpty('sub', true),
74
            new Specification\LesserOrEqualsTo('nbf'),
75
            new Specification\EqualsTo('jti'),
76
            new Specification\EqualsTo('azp'),
77
            new Specification\EqualsTo('nonce'),
78
        ]);
79
80
        $this->router = $router;
81
82
        parent::__construct($options, $collaborators);
83
        $this->buildParams($options);
84
    }
85
86
    private function buildParams(array $options = [])
87
    {
88
        if (!empty($options)) {
89
            $this->clientId = $options['client_key'];
90
            $this->clientSecret = $options['client_secret'];
91
            $this->idTokenIssuer = $options['id_token_issuer'];
92
            $this->publicKey = 'file://'.$options['public_key'];
93
            $this->state = $this->getRandomState();
94
            $this->baseUri = $options['base_uri'];
95
            $this->useSession = $options['use_session'];
96
            $url = null;
97
            switch ($options['redirect']['type']) {
98
                case 'uri':
99
                    $url = $options['redirect']['uri'];
100
                    break;
101
                case 'route':
102
                    $params = !empty($options['redirect']['params']) ? $options['redirect']['params'] : [];
103
                    $url = $this->router->generate($options['redirect']['route'], $params, UrlGeneratorInterface::ABSOLUTE_URL);
104
                    break;
105
            }
106
            $this->redirectUri = $url;
107
108
            /** @var $options array[] */
109
            foreach ($options['uris'] as $name => $uri) {
110
                $opt = [
111
                    'client_id' => $this->clientId,
112
                    'redirect_uri' => $this->redirectUri,
113
                    'state' => $this->state,
114
                    'base_uri' => $this->baseUri,
115
                ];
116
                $this->uris[$name] = new Uri($uri, $opt);
117
            }
118
        }
119
    }
120
121
    /**
122
     * Requests an access token using a specified grant and option set.
123
     *
124
     * @param  mixed $grant
125
     * @param  array $options
126
     *
127
     * @return BaseAccessToken
128
     * @throws InvalidTokenException
129
     * @throws \BadMethodCallException
130
     */
131
    public function getAccessToken($grant, array $options = [])
132
    {
133
        /** @var Token $token */
134
        $accessToken = parent::getAccessToken($grant, $options);
135
136
        if (null === $accessToken) {
137
            throw new InvalidTokenException('Invalid access token.');
138
        }
139
140
        $token = $accessToken->getIdToken();
0 ignored issues
show
Bug introduced by
The method getIdToken() does not exist on League\OAuth2\Client\Token\AccessToken. Did you maybe mean getToken()? ( Ignorable by Annotation )

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

140
        /** @scrutinizer ignore-call */ 
141
        $token = $accessToken->getIdToken();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
141
142
        // id_token is empty.
143
        if (null === $token) {
144
            throw new InvalidTokenException('Expected an id_token but did not receive one from the authorization server.');
145
        }
146
147
        // If the ID Token is received via direct communication between the Client and the Token Endpoint
148
        // (which it is in this flow), the TLS server validation MAY be used to validate the issuer in place of checking
149
        // the token signature. The Client MUST validate the signature of all other ID Tokens according to JWS [JWS]
150
        // using the algorithm specified in the JWT alg Header Parameter. The Client MUST use the keys provided by
151
        // the Issuer.
152
        //
153
        // The alg value SHOULD be the default of RS256 or the algorithm sent by the Client in the
154
        // id_token_signed_response_alg parameter during Registration.
155
        if (false === $token->verify($this->signer, $this->getPublicKey())) {
156
            throw new InvalidTokenException('Received an invalid id_token from authorization server.');
157
        }
158
159
        // validations
160
        // @see http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
161
        // validate the iss (issuer)
162
        // - The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
163
        // MUST exactly match the value of the iss (issuer) Claim.
164
        // validate the aud
165
        // - The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer
166
        // identified by the iss (issuer) Claim as an audience. The aud (audience) Claim MAY contain an array with more
167
        // than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
168
        // or if it contains additional audiences not trusted by the Client.
169
        // - If a nonce value was sent in the Authentication Request, a nonce Claim MUST be present and its value checked
170
        // to verify that it is the same value as the one that was sent in the Authentication Request. The Client SHOULD
171
        // check the nonce value for replay attacks. The precise method for detecting replay attacks is Client specific.
172
        // - If the auth_time Claim was requested, either through a specific request for this Claim or by using
173
        // the max_age parameter, the Client SHOULD check the auth_time Claim value and request re-authentication if it
174
        // determines too much time has elapsed since the last End-User authentication.
175
        // TODO
176
        // If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.
177
        // The meaning and processing of acr Claim Values is out of scope for this specification.
178
        $currentTime = time();
179
        $data = [
180
            'iss' => $this->getIdTokenIssuer(),
181
            'exp' => $currentTime,
182
            'auth_time' => $currentTime,
183
            'iat' => $currentTime,
184
            'nbf' => $currentTime,
185
            'aud' => $this->clientId,
186
        ];
187
188
        // If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
189
        // If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
190
        if ($token->hasClaim('azp')) {
191
            $data['azp'] = $this->clientId;
192
        }
193
194
        if (false === $this->validatorChain->validate($data, $token)) {
195
            throw new InvalidTokenException('The id_token did not pass validation.');
196
        }
197
198
        if ($this->useSession) {
199
            $_SESSION['access_token'] = $accessToken->getToken();
200
            $_SESSION['refresh_token'] = $accessToken->getRefreshToken();
201
            $_SESSION['id_token'] = $accessToken->getIdTokenHint();
0 ignored issues
show
introduced by
The method getIdTokenHint() does not exist on League\OAuth2\Client\Token\AccessToken. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

201
            /** @scrutinizer ignore-call */ 
202
            $_SESSION['id_token'] = $accessToken->getIdTokenHint();
Loading history...
202
        }
203
204
        return $accessToken;
205
    }
206
207
    public function getPublicKey()
208
    {
209
        return new Key($this->publicKey);
210
    }
211
212
    /**
213
     * Get the issuer of the OpenID Connect id_token
214
     *
215
     * @return string
216
     */
217
    protected function getIdTokenIssuer()
218
    {
219
        return $this->idTokenIssuer;
220
    }
221
222
    /**
223
     * @return Specification\ValidatorChain
224
     */
225
    public function getValidatorChain()
226
    {
227
        return $this->validatorChain;
228
    }
229
230
    public function getBaseAuthorizationUrl()
231
    {
232
        return '';
233
    }
234
235
    public function getBaseAccessTokenUrl(array $params)
236
    {
237
        return '';
238
    }
239
240
    public function getDefaultScopes()
241
    {
242
        return [];
243
    }
244
245
    public function getResourceOwnerDetailsUrl(BaseAccessToken $token)
246
    {
247
    }
248
249
    public function getUri($name)
250
    {
251
        return $this->uris[$name];
252
    }
253
254
    /**
255
     * Returns all options that are required.
256
     *
257
     * @return array
258
     */
259
    protected function getRequiredOptions()
260
    {
261
        return [];
262
    }
263
264
    /**
265
     * Overload parent as OpenID Connect specification states scopes shall be separated by spaces
266
     *
267
     * @return string
268
     */
269
    protected function getScopeSeparator()
270
    {
271
        return ' ';
272
    }
273
274
    /**
275
     * Creates an access token from a response.
276
     *
277
     * The grant that was used to fetch the response can be used to provide
278
     * additional context.
279
     *
280
     * @param  array             $response
281
     *
282
     * @param AbstractGrant|null $grant
283
     *
284
     * @return AccessToken
285
     */
286
    protected function createAccessToken(array $response, AbstractGrant $grant = null)
287
    {
288
        if ($this->check()) {
289
            return new AccessToken($response);
290
        }
291
292
        return null;
293
    }
294
295
    public function check()
296
    {
297
        return true;
298
    }
299
300
    protected function createResourceOwner(array $response, BaseAccessToken $token)
301
    {
302
        return [];
303
    }
304
305
    protected function checkResponse(ResponseInterface $response, $data)
306
    {
307
    }
308
}
309