Completed
Push — master ( e8dfd9...f2d868 )
by Steven
03:14
created

Keycloak::setEncryptionKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 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
    /**
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 26
    public function __construct(array $options = [], array $collaborators = [])
62
    {
63 26
        if (isset($options['encryptionKeyPath'])) {
64 2
            $this->setEncryptionKeyPath($options['encryptionKeyPath']);
65 2
            unset($options['encryptionKeyPath']);
66
        }
67 26
        parent::__construct($options, $collaborators);
68 26
    }
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 4
            if ($this->encryptionAlgorithm && $this->encryptionKey) {
81 2
                $response = json_decode(
82 2
                    json_encode(
83 2
                        JWT::decode(
84 2
                            $response,
85 2
                            $this->encryptionKey,
86 2
                            array($this->encryptionAlgorithm)
87
                        )
88
                    ),
89 2
                    true
90
                );
91
            } else {
92 2
                throw new EncryptionConfigurationException(
93
                    'The given response may be encrypted and sufficient '.
94 2
                    'encryption configuration has not been provided.',
95 2
                    400
96
                );
97
            }
98
        }
99
100 4
        return $response;
101
    }
102
103
    /**
104
     * Get authorization url to begin OAuth flow
105
     *
106
     * @return string
107
     */
108 6
    public function getBaseAuthorizationUrl()
109
    {
110 6
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/auth';
111
    }
112
113
    /**
114
     * Get access token url to retrieve token
115
     *
116
     * @param  array $params
117
     *
118
     * @return string
119
     */
120 12
    public function getBaseAccessTokenUrl(array $params)
121
    {
122 12
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/token';
123
    }
124
125
    /**
126
     * Get provider url to fetch user details
127
     *
128
     * @param  AccessToken $token
129
     *
130
     * @return string
131
     */
132 6
    public function getResourceOwnerDetailsUrl(AccessToken $token)
133
    {
134 6
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/userinfo';
135
    }
136
137
    /**
138
     * Builds the logout URL.
139
     *
140
     * @param array $options
141
     * @return string Authorization URL
142
     */
143 2
    public function getLogoutUrl(array $options = [])
144
    {
145 2
        $base = $this->getBaseLogoutUrl();
146 2
        $params = $this->getAuthorizationParameters($options);
147 2
        $query = $this->getAuthorizationQuery($params);
148 2
        return $this->appendQuery($base, $query);
149
    }
150
151
    /**
152
     * Get logout url to logout of session token
153
     *
154
     * @return string
155
     */
156 2
    private function getBaseLogoutUrl()
157
    {
158 2
        return $this->getBaseUrlWithRealm() . '/protocol/openid-connect/logout';
159
    }
160
161
    /**
162
     * Creates base url from provider configuration.
163
     *
164
     * @return string
165
     */
166 20
    protected function getBaseUrlWithRealm()
167
    {
168 20
        return $this->authServerUrl.'/realms/'.$this->realm;
169
    }
170
171
    /**
172
     * Get the default scopes used by this provider.
173
     *
174
     * This should not be a complete list of all scopes, but the minimum
175
     * required for the provider user interface!
176
     *
177
     * @return string[]
178
     */
179 6
    protected function getDefaultScopes()
180
    {
181 6
        return ['name', 'email'];
182
    }
183
184
    /**
185
     * Check a provider response for errors.
186
     *
187
     * @throws IdentityProviderException
188
     * @param  ResponseInterface $response
189
     * @param  string $data Parsed response data
190
     * @return void
191
     */
192 10
    protected function checkResponse(ResponseInterface $response, $data)
193
    {
194 10
        if (!empty($data['error'])) {
195 2
            $error = $data['error'].': '.$data['error_description'];
196 2
            throw new IdentityProviderException($error, 0, $data);
197
        }
198 8
    }
199
200
    /**
201
     * Generate a user object from a successful user details request.
202
     *
203
     * @param array $response
204
     * @param AccessToken $token
205
     * @return KeycloakResourceOwner
206
     */
207 4
    protected function createResourceOwner(array $response, AccessToken $token)
208
    {
209 4
        return new KeycloakResourceOwner($response);
210
    }
211
212
    /**
213
     * Requests and returns the resource owner of given access token.
214
     *
215
     * @param  AccessToken $token
216
     * @return KeycloakResourceOwner
217
     */
218 6
    public function getResourceOwner(AccessToken $token)
219
    {
220 6
        $response = $this->fetchResourceOwnerDetails($token);
221
222 6
        $response = $this->decryptResponse($response);
223
224 4
        return $this->createResourceOwner($response, $token);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->decryptResponse($response) on line 222 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...
225
    }
226
227
    /**
228
     * Updates expected encryption algorithm of Keycloak instance.
229
     *
230
     * @param string  $encryptionAlgorithm
231
     *
232
     * @return Keycloak
233
     */
234 4
    public function setEncryptionAlgorithm($encryptionAlgorithm)
235
    {
236 4
        $this->encryptionAlgorithm = $encryptionAlgorithm;
237
238 4
        return $this;
239
    }
240
241
    /**
242
     * Updates expected encryption key of Keycloak instance.
243
     *
244
     * @param string  $encryptionKey
245
     *
246
     * @return Keycloak
247
     */
248 4
    public function setEncryptionKey($encryptionKey)
249
    {
250 4
        $this->encryptionKey = $encryptionKey;
251
252 4
        return $this;
253
    }
254
255
    /**
256
     * Updates expected encryption key of Keycloak instance to content of given
257
     * file path.
258
     *
259
     * @param string  $encryptionKeyPath
260
     *
261
     * @return Keycloak
262
     */
263 2
    public function setEncryptionKeyPath($encryptionKeyPath)
264
    {
265
        try {
266 2
            $this->encryptionKey = file_get_contents($encryptionKeyPath);
267
        } catch (Exception $e) {
268
            // Not sure how to handle this yet.
269
        }
270
271 2
        return $this;
272
    }
273
}
274