Keycloak::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

254
        return $this->createResourceOwner(/** @scrutinizer ignore-type */ $response, $token);
Loading history...
255
    }
256
257
    /**
258
     * Updates expected encryption algorithm of Keycloak instance.
259
     *
260
     * @param string $encryptionAlgorithm algorithm to be used
261
     *
262
     * @return Keycloak
263
     */
264 2
    public function setEncryptionAlgorithm($encryptionAlgorithm)
265
    {
266 2
        $this->encryptionAlgorithm = $encryptionAlgorithm;
267
268 2
        return $this;
269
    }
270
271
    /**
272
     * Updates expected encryption key of Keycloak instance.
273
     *
274
     * @param string $encryptionKey encryption key to use
275
     *
276
     * @return Keycloak
277
     */
278 2
    public function setEncryptionKey($encryptionKey)
279
    {
280 2
        $this->encryptionKey = $encryptionKey;
281
282 2
        return $this;
283
    }
284
285
    /**
286
     * Updates expected encryption key of Keycloak instance to content of given
287
     * file path.
288
     *
289
     * @param string $encryptionKeyPath encryption key path to use
290
     *
291
     * @return Keycloak
292
     */
293 1
    public function setEncryptionKeyPath(string $encryptionKeyPath)
294
    {
295
        try {
296 1
            $this->encryptionKey = file_get_contents($encryptionKeyPath);
297
        } catch (Exception $e) {
298
            // Not sure how to handle this yet.
299
        }
300
301 1
        return $this;
302
    }
303
304
    /**
305
     * Checks if provider is configured to use encryption.
306
     *
307
     * @return bool
308
     */
309 2
    public function usesEncryption()
310
    {
311 2
        return (bool) $this->encryptionAlgorithm && $this->encryptionKey;
312
    }
313
}
314