Completed
Push — master ( fbe9c7...7a69a3 )
by Irsad
12:36
created

Keycloak::getBaseUrlWithRealm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace IrsadArief\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 IrsadArief\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
    public function __construct(array $options = [], array $collaborators = [])
62
    {
63
        if (isset($options['encryptionKeyPath'])) {
64
            $this->setEncryptionKeyPath($options['encryptionKeyPath']);
65
            unset($options['encryptionKeyPath']);
66
        }
67
        parent::__construct($options, $collaborators);
68
    }
69
70
    /**
71
     * Attempts to decrypt the given response.
72
     *
73
     * @param  string|array|null $response
74
     *
0 ignored issues
show
Bug introduced by
There is no parameter named $response. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
75
     * @return string|array|null
76
     */
77
78
    public function verifyUser(array $provider){
79
        if (!isset($_GET['code'])) {
80
81
            // If we don't have an authorization code then get one
82
            $authUrl = $provider->getAuthorizationUrl();
0 ignored issues
show
Bug introduced by
The method getAuthorizationUrl cannot be called on $provider (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
83
            $_SESSION['oauth2state'] = $provider->getState();
0 ignored issues
show
Bug introduced by
The method getState cannot be called on $provider (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
84
            redirect($authUrl);
85
            exit;
86
        
87
        // Check given state against previously stored one to mitigate CSRF attack
88
        } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
89
90
            unset($_SESSION['oauth2state']);
91
            exit('Invalid state, make sure HTTP sessions are enabled.');
92
93
        } else {
94
95
            // Try to get an access token (using the authorization coe grant)
96
            try {
97
                $token = $provider->getAccessToken('authorization_code', [
0 ignored issues
show
Bug introduced by
The method getAccessToken cannot be called on $provider (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
98
                    'code' => $_GET['code']
99
                ]);
100
                return $token;
101
            } catch (Exception $e) {
102
                exit('Failed to get access token: '.$e->getMessage());
103
            }
104
105
        }
106
    }
107
    public function userDetails(AccessToken $token,array $provider) {
108
        // Optional: Now you have a token you can look up a users profile data
109
            try {
110
111
                // We got an access token, let's now get the user's details
112
                $user = $provider->getResourceOwner($token);
0 ignored issues
show
Bug introduced by
The method getResourceOwner cannot be called on $provider (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
113
                return $user;
114
                // Use these details to create a new profile
115
                //printf('Hello %s! ', $user->getName());
116
117
                // echo "Nama : ".$user->getName();
118
                // echo "Nama Depan : ".$user->getNamaDepan();
119
                // echo "Nama Belakang : ".$user->getNamaBelakang();
120
                // echo "NIP : ".$user->getNip();
121
                // echo "Username : ".$user->getUsername();
122
                // echo "Email : ".$user->getEmail();
123
                //print_r($details_url);
124
125
            } catch (Exception $e) {
126
                return('Failed to get resource owner: '.$e->getMessage());
127
            }
128
129
            // Use this to interact with an API on the users behalf
130
            //echo $token->getToken();
131
    }
132
    public function decryptResponse($response)
133
    {
134
        if (!is_string($response)) {
135
            return $response;
136
        }
137
138
        if ($this->usesEncryption()) {
139
            return json_decode(
140
                json_encode(
141
                    JWT::decode(
142
                        $response,
143
                        $this->encryptionKey,
144
                        array($this->encryptionAlgorithm)
145
                    )
146
                ),
147
                true
148
            );
149
        }
150
151
        throw EncryptionConfigurationException::undeterminedEncryption();
152
    }
153
154
    /**
155
     * Get authorization url to begin OAuth flow
156
     *
157
     * @return string
158
     */
159
    public function getBaseAuthorizationUrl()
160
    {
161
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/auth';
162
    }
163
164
    /**
165
     * Get access token url to retrieve token
166
     *
167
     * @param  array $params
168
     *
169
     * @return string
170
     */
171
    public function getBaseAccessTokenUrl(array $params)
172
    {
173
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/token';
174
    }
175
176
    /**
177
     * Get provider url to fetch user details
178
     *
179
     * @param  AccessToken $token
180
     *
181
     * @return string
182
     */
183
    public function getResourceOwnerDetailsUrl(AccessToken $token)
184
    {
185
        return $this->getBaseUrlWithRealm().'/protocol/openid-connect/userinfo';
186
    }
187
188
    /**
189
     * Builds the logout URL.
190
     *
191
     * @param array $options
192
     * @return string Authorization URL
193
     */
194
    public function getLogoutUrl(array $options = [])
195
    {
196
        $base = $this->getBaseLogoutUrl();
197
        $params = $this->getAuthorizationParameters($options);
198
        $query = $this->getAuthorizationQuery($params);
199
        return $this->appendQuery($base, $query);
200
    }
201
202
    /**
203
     * Get logout url to logout of session token
204
     *
205
     * @return string
206
     */
207
    private function getBaseLogoutUrl()
208
    {
209
        return $this->getBaseUrlWithRealm() . '/protocol/openid-connect/logout';
210
    }
211
212
    /**
213
     * Creates base url from provider configuration.
214
     *
215
     * @return string
216
     */
217
    protected function getBaseUrlWithRealm()
218
    {
219
        return $this->authServerUrl.'/auth/realms/'.$this->realm;
220
    }
221
222
    /**
223
     * Get the default scopes used by this provider.
224
     *
225
     * This should not be a complete list of all scopes, but the minimum
226
     * required for the provider user interface!
227
     *
228
     * @return string[]
229
     */
230
    protected function getDefaultScopes()
231
    {
232
        return ['name', 'email'];
233
    }
234
235
    /**
236
     * Check a provider response for errors.
237
     *
238
     * @throws IdentityProviderException
239
     * @param  ResponseInterface $response
240
     * @param  string $data Parsed response data
241
     * @return void
242
     */
243
    protected function checkResponse(ResponseInterface $response, $data)
244
    {
245
        if (!empty($data['error'])) {
246
            $error = $data['error'].': '.$data['error_description'];
247
            throw new IdentityProviderException($error, 0, $data);
248
        }
249
    }
250
251
    /**
252
     * Generate a user object from a successful user details request.
253
     *
254
     * @param array $response
255
     * @param AccessToken $token
256
     * @return KeycloakResourceOwner
257
     */
258
    protected function createResourceOwner(array $response, AccessToken $token)
259
    {
260
        return new KeycloakResourceOwner($response);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \IrsadArief\O...sourceOwner($response); (IrsadArief\OAuth2\Client...r\KeycloakResourceOwner) is incompatible with the return type declared by the abstract method League\OAuth2\Client\Pro...er::createResourceOwner of type League\OAuth2\Client\Pro...\ResourceOwnerInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
261
    }
262
263
    /**
264
     * Requests and returns the resource owner of given access token.
265
     *
266
     * @param  AccessToken $token
267
     * @return KeycloakResourceOwner
268
     */
269
    public function getResourceOwner(AccessToken $token)
270
    {
271
        $response = $this->fetchResourceOwnerDetails($token);
272
273
        $response = $this->decryptResponse($response);
274
275
        return $this->createResourceOwner($response, $token);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createReso...ner($response, $token); (IrsadArief\OAuth2\Client...r\KeycloakResourceOwner) is incompatible with the return type of the parent method League\OAuth2\Client\Pro...vider::getResourceOwner of type League\OAuth2\Client\Pro...\ResourceOwnerInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
276
    }
277
278
    /**
279
     * Updates expected encryption algorithm of Keycloak instance.
280
     *
281
     * @param string  $encryptionAlgorithm
282
     *
283
     * @return Keycloak
284
     */
285
    public function setEncryptionAlgorithm($encryptionAlgorithm)
286
    {
287
        $this->encryptionAlgorithm = $encryptionAlgorithm;
288
289
        return $this;
290
    }
291
292
    /**
293
     * Updates expected encryption key of Keycloak instance.
294
     *
295
     * @param string  $encryptionKey
296
     *
297
     * @return Keycloak
298
     */
299
    public function setEncryptionKey($encryptionKey)
300
    {
301
        $this->encryptionKey = $encryptionKey;
302
303
        return $this;
304
    }
305
306
    /**
307
     * Updates expected encryption key of Keycloak instance to content of given
308
     * file path.
309
     *
310
     * @param string  $encryptionKeyPath
311
     *
312
     * @return Keycloak
313
     */
314
    public function setEncryptionKeyPath($encryptionKeyPath)
315
    {
316
        try {
317
            $this->encryptionKey = file_get_contents($encryptionKeyPath);
318
        } catch (Exception $e) {
319
            // Not sure how to handle this yet.
320
        }
321
322
        return $this;
323
    }
324
325
    /**
326
     * Checks if provider is configured to use encryption.
327
     *
328
     * @return bool
329
     */
330
    public function usesEncryption()
331
    {
332
        return (bool) $this->encryptionAlgorithm && $this->encryptionKey;
333
    }
334
}
335