These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 | * |
||
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(); |
||
83 | $_SESSION['oauth2state'] = $provider->getState(); |
||
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', [ |
||
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); |
||
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
|
|||
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
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
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 |
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.