Completed
Pull Request — master (#28)
by
unknown
11:13
created

Keycloak   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 24
lcom 2
cbo 6
dl 0
loc 288
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A usesEncryption() 0 4 2
A getResourceOwner() 0 8 1
A setEncryptionAlgorithm() 0 6 1
A setEncryptionKey() 0 6 1
A setEncryptionKeyPath() 0 10 2
A decryptResponse() 0 21 3
A getBaseAuthorizationUrl() 0 4 1
A getBaseAccessTokenUrl() 0 4 1
A getResourceOwnerDetailsUrl() 0 4 1
A getLogoutUrl() 0 9 1
A getBaseLogoutUrl() 0 4 1
A getBaseUrlWithRealm() 0 4 1
A getDefaultScopes() 0 4 1
A checkResponse() 0 10 3
A createResourceOwner() 0 4 1
A buildOpenIdConnectUrl() 0 4 1
1
<?php
2
3
namespace Stevenmaguire\OAuth2\Client\Provider;
4
5
use Exception;
6
use Firebase\JWT\JWT;
7
use League\OAuth2\Client\Provider\AbstractProvider;
8
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
9
use League\OAuth2\Client\Token\AccessToken;
10
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
11
use Psr\Http\Message\ResponseInterface;
12
use Stevenmaguire\OAuth2\Client\Provider\Exception\EncryptionConfigurationException;
13
14
class Keycloak extends AbstractProvider
15
{
16
    use BearerAuthorizationTrait;
17
18
    const OPEN_ID_PROTOCOL_URI = '/protocol/openid-connect/';
19
20
    /**
21
     * Keycloak URL, eg. http://localhost:8080/auth.
22
     *
23
     * @var string
24
     */
25
    public $authServerUrl = null;
26
27
    /**
28
     * Realm name, eg. demo.
29
     *
30
     * @var string
31
     */
32
    public $realm = null;
33
34
    /**
35
     * Encryption algorithm.
36
     *
37
     * You must specify supported algorithms for your application. See
38
     * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
39
     * for a list of spec-compliant algorithms.
40
     *
41
     * @var string
42
     */
43
    public $encryptionAlgorithm = null;
44
45
    /**
46
     * Encryption key.
47
     *
48
     * @var string
49
     */
50
    public $encryptionKey = null;
51
52
    /**
53
     * Constructs an OAuth 2.0 service provider.
54
     *
55
     * @param array $options An array of options to set on this provider.
56
     *     Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
57
     *     Individual providers may introduce more options, as needed.
58
     * @param array $collaborators An array of collaborators that may be used to
59
     *     override this provider's default behavior. Collaborators include
60
     *     `grantFactory`, `requestFactory`, `httpClient`, and `randomFactory`.
61 28
     *     Individual providers may introduce more collaborators, as needed.
62
     */
63 28
    public function __construct(array $options = [], array $collaborators = [])
64 4
    {
65 4
        if (isset($options['encryptionKeyPath'])) {
66
            $this->setEncryptionKeyPath($options['encryptionKeyPath']);
67 28
            unset($options['encryptionKeyPath']);
68 28
        }
69
        parent::__construct($options, $collaborators);
70
    }
71
72
    /**
73
     * Attempts to decrypt the given response.
74
     *
75
     * @param  string|array|null $response
76
     *
77 6
     * @return string|array|null
78
     *
79 6
     * @throws EncryptionConfigurationException
80 2
     */
81
    public function decryptResponse($response)
82
    {
83 4
        if (!is_string($response)) {
84 2
            return $response;
85 2
        }
86 2
87 2
        if ($this->usesEncryption()) {
88 2
            return json_decode(
89 2
                json_encode(
90
                    JWT::decode(
91
                        $response,
92 2
                        $this->encryptionKey,
93
                        array($this->encryptionAlgorithm)
94
                    )
95
                ),
96 2
                true
97
            );
98
        }
99
100
        throw EncryptionConfigurationException::undeterminedEncryption();
101
    }
102
103
    /**
104 6
     * Get authorization url to begin OAuth flow
105
     *
106 6
     * @return string
107
     */
108
    public function getBaseAuthorizationUrl()
109
    {
110
        return $this->buildOpenIdConnectUrl('auth');
111
    }
112
113
    /**
114
     * Get access token url to retrieve token
115
     *
116 12
     * @param  array $params
117
     *
118 12
     * @return string
119
     */
120
    public function getBaseAccessTokenUrl(array $params)
121
    {
122
        return $this->buildOpenIdConnectUrl('token');
123
    }
124
125
    /**
126
     * Get provider url to fetch user details
127
     *
128 6
     * @param  AccessToken $token
129
     *
130 6
     * @return string
131
     */
132
    public function getResourceOwnerDetailsUrl(AccessToken $token)
133
    {
134
        return $this->buildOpenIdConnectUrl('userinfo');
135
    }
136
137
    /**
138
     * Builds the logout URL.
139 2
     *
140
     * @param array $options
141 2
     * @return string Authorization URL
142 2
     */
143 2
    public function getLogoutUrl(array $options = [])
144 2
    {
145
        return $this->appendQuery(
146
            $this->getBaseLogoutUrl(),
147
            $this->getAuthorizationQuery(
148
                $this->getAuthorizationParameters($options)
149
            )
150
        );
151
    }
152 2
153
    /**
154 2
     * Get logout url to logout of session token
155
     *
156
     * @return string
157
     */
158
    private function getBaseLogoutUrl()
159
    {
160
        return $this->buildOpenIdConnectUrl('logout');
161
    }
162 20
163
    /**
164 20
     * Creates base url from provider configuration.
165
     *
166
     * @return string
167
     */
168
    protected function getBaseUrlWithRealm()
169
    {
170
        return $this->authServerUrl.'/realms/'.$this->realm;
171
    }
172
173
    /**
174
     * Get the default scopes used by this provider.
175 6
     *
176
     * This should not be a complete list of all scopes, but the minimum
177 6
     * required for the provider user interface!
178
     *
179
     * @return string[]
180
     */
181
    protected function getDefaultScopes()
182
    {
183
        return ['name', 'email'];
184
    }
185
186
    /**
187
     * Check a provider response for errors.
188 10
     *
189
     * @throws IdentityProviderException
190 10
     * @param  ResponseInterface $response
191 2
     * @param  string|array $data Parsed response data
192 2
     * @return void
193
     */
194 8
    protected function checkResponse(ResponseInterface $response, $data)
195
    {
196
        if (\is_array($data) && !empty($data['error'])) {
197
            throw new IdentityProviderException(
198
                \sprintf('%s: %s', $data['error'], $data['error_description']),
199
                0,
200
                $data
201
            );
202
        }
203 4
    }
204
205 4
    /**
206
     * Generate a user object from a successful user details request.
207
     *
208
     * @param array $response
209
     * @param AccessToken $token
210
     * @return KeycloakResourceOwner
211
     */
212
    protected function createResourceOwner(array $response, AccessToken $token)
213
    {
214 6
        return new KeycloakResourceOwner($response);
215
    }
216 6
217
    /**
218 6
     * Requests and returns the resource owner of given access token.
219
     *
220 4
     * @param AccessToken $token
221
     *
222
     * @return KeycloakResourceOwner
223
     *
224
     * @throws EncryptionConfigurationException
225
     */
226
    public function getResourceOwner(AccessToken $token)
227
    {
228
        $response = $this->fetchResourceOwnerDetails($token);
229
230 4
        $response = $this->decryptResponse($response);
231
232 4
        return $this->createResourceOwner($response, $token);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->decryptResponse($response) on line 230 can also be of type null or string; however, Stevenmaguire\OAuth2\Cli...::createResourceOwner() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
233
    }
234 4
235
    /**
236
     * Updates expected encryption algorithm of Keycloak instance.
237
     *
238
     * @param string  $encryptionAlgorithm
239
     *
240
     * @return Keycloak
241
     */
242
    public function setEncryptionAlgorithm($encryptionAlgorithm)
243
    {
244 4
        $this->encryptionAlgorithm = $encryptionAlgorithm;
245
246 4
        return $this;
247
    }
248 4
249
    /**
250
     * Updates expected encryption key of Keycloak instance.
251
     *
252
     * @param string  $encryptionKey
253
     *
254
     * @return Keycloak
255
     */
256
    public function setEncryptionKey($encryptionKey)
257
    {
258
        $this->encryptionKey = $encryptionKey;
259 4
260
        return $this;
261
    }
262 4
263 2
    /**
264
     * Updates expected encryption key of Keycloak instance to content of given
265
     * file path.
266
     *
267 4
     * @param string  $encryptionKeyPath
268
     *
269
     * @return Keycloak
270
     */
271
    public function setEncryptionKeyPath($encryptionKeyPath)
272
    {
273
        try {
274
            $this->encryptionKey = file_get_contents($encryptionKeyPath);
275 4
        } catch (Exception $e) {
276
            // Not sure how to handle this yet.
277 4
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * Checks if provider is configured to use encryption.
284
     *
285
     * @return bool
286
     */
287
    public function usesEncryption()
288
    {
289
        return (bool) $this->encryptionAlgorithm && $this->encryptionKey;
290
    }
291
292
    /**
293
     * @param string $uriPostfix
294
     *
295
     * @return string
296
     */
297
    private function buildOpenIdConnectUrl($uriPostfix)
298
    {
299
        return $this->getBaseUrlWithRealm() . self::OPEN_ID_PROTOCOL_URI . $uriPostfix;
300
    }
301
}
302