Completed
Pull Request — master (#23)
by
unknown
14:57
created

Keycloak   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 100%

Importance

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