Skip to content

Commit 59b1fc6

Browse files
chore: improve testing and override fetch url to use Guzzle (#21)
* chore: improve testing * Update description of getRoute function * Move descriptions to docblock * Add additional test for nonce, state and code challenge * Cleanup * Override fetchUrl and add test for id token logic * Generate JWT in test * Run linter * Improve test with signed id token * Reformat headers * Add additional request tests * Add some description * Use tls_verify config option for ca path
1 parent 71bf08f commit 59b1fc6

File tree

6 files changed

+402
-14
lines changed

6 files changed

+402
-14
lines changed

config/oidc.php

+7-3
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,13 @@
8181
],
8282

8383
/**
84-
* TLS Verify
85-
* Can be disabled for local development.
86-
* Is used in OpenIDConfigurationLoader and in the ServiceProvider for OpenIDConnectClient.
84+
* TLS Verify - Used as the verify option for Guzzle.
85+
*
86+
* Default is true and verifies the certificate and uses the default CA bundle of the system.
87+
* When set to `false` it disables the certificate verification (this is insecure!).
88+
* When set to a path of a CA bundle, the custom certificate is used.
89+
*
90+
* @link https://docs.guzzlephp.org/en/latest/request-options.html#verify
8791
*/
8892
'tls_verify' => env('OIDC_TLS_VERIFY', true),
8993
];

src/OpenIDConfiguration/OpenIDConfigurationLoader.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function __construct(
1313
protected string $issuer,
1414
protected ?Repository $cacheStore = null,
1515
protected int $cacheTtl = 3600,
16-
protected bool $tlsVerify = true,
16+
protected bool|string $tlsVerify = true,
1717
) {
1818
}
1919

@@ -39,9 +39,9 @@ protected function getConfigurationFromIssuer(): OpenIDConfiguration
3939
$url = $this->getOpenIDConfigurationUrl();
4040

4141
$response = Http::baseUrl($this->issuer)
42-
->when(!$this->tlsVerify, function ($pendingRequest) {
43-
return $pendingRequest->withoutVerifying();
44-
})
42+
->withOptions([
43+
'verify' => $this->tlsVerify
44+
])
4545
->get($url);
4646

4747
if (!$response->successful()) {

src/OpenIDConnectClient.php

+103-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Illuminate\Http\Exceptions\HttpResponseException;
88
use Illuminate\Http\RedirectResponse;
9+
use Illuminate\Support\Facades\Http;
910
use Illuminate\Support\Facades\Session;
1011
use Illuminate\Support\Str;
1112
use Jumbojett\OpenIDConnectClientException;
@@ -19,6 +20,15 @@ class OpenIDConnectClient extends \Jumbojett\OpenIDConnectClient
1920
{
2021
protected ?JweDecryptInterface $jweDecrypter;
2122
protected ?OpenIDConfiguration $openIDConfiguration;
23+
/**
24+
* @var int|null Response code from the server
25+
*/
26+
protected ?int $responseCode;
27+
28+
/**
29+
* @var string|null Content type from the server
30+
*/
31+
private ?string $responseContentType;
2232

2333
public function __construct(
2434
?string $providerUrl = null,
@@ -95,7 +105,6 @@ protected function handleJweResponse($jwe): string
95105
* @param string|string[]|bool|null $default optional
96106
* @throws OpenIDConnectClientException
97107
* @return string|string[]|bool
98-
* @psalm-suppress ImplementedReturnTypeMismatch
99108
*/
100109
protected function getWellKnownConfigValue($param, $default = null): string|array|bool
101110
{
@@ -156,4 +165,97 @@ protected function getAuthorizationEndpoint(): string
156165

157166
return $authorizationEndpoint;
158167
}
168+
169+
/**
170+
* Override the fetchURL method to use Laravel HTTP client.
171+
* This uses Guzzle in the background.
172+
*
173+
* @param string $url
174+
* @param string | null $post_body string If this is set the post type will be POST
175+
* @param array<array-key, mixed> $headers Extra headers to be sent with the request.
176+
* @return string
177+
* @throws OpenIDConnectClientException
178+
*/
179+
protected function fetchURL(string $url, string $post_body = null, array $headers = []): string
180+
{
181+
$pendingRequest = Http::withUserAgent($this->getUserAgent())
182+
->timeout($this->timeOut)
183+
->withOptions([
184+
'verify' => $this->getCertPath() ?: $this->getVerifyPeer()
185+
]);
186+
187+
if (count($headers) > 0) {
188+
$pendingRequest->withHeaders($this->reformatHeaders($headers));
189+
}
190+
191+
if ($post_body === null) {
192+
$request = $pendingRequest->get($url);
193+
} else {
194+
$isJson = is_object(json_decode($post_body, false));
195+
196+
$request = $pendingRequest
197+
->withBody($post_body, $isJson ? 'application/json' : 'application/x-www-form-urlencoded')
198+
->post($url);
199+
}
200+
201+
$this->responseCode = $request->status();
202+
$this->responseContentType = $request->header('Content-Type');
203+
204+
if ($request->failed()) {
205+
throw new OpenIDConnectClientException(
206+
'Request failed with status code ' . $this->responseCode . ' ' . $request->reason()
207+
);
208+
}
209+
210+
return $request->body();
211+
}
212+
213+
/**
214+
* Get the response code from last action/curl request.
215+
*
216+
* @return int
217+
*/
218+
public function getResponseCode(): int
219+
{
220+
return $this->responseCode ?? 0;
221+
}
222+
223+
/**
224+
* Get the content type from last action/curl request.
225+
*
226+
* @return string|null
227+
*/
228+
public function getResponseContentType(): ?string
229+
{
230+
return $this->responseContentType;
231+
}
232+
233+
public function setTlsVerify(bool|string $tlsVerify): void
234+
{
235+
$verify = (bool)$tlsVerify;
236+
$this->setVerifyHost($verify);
237+
$this->setVerifyPeer($verify);
238+
239+
if (is_string($tlsVerify)) {
240+
$this->setCertPath($tlsVerify);
241+
}
242+
}
243+
244+
/**
245+
* Reformat the headers from string to array for Guzzle.
246+
*
247+
* @param array<string> $headers
248+
* @return array<string, array<int, string>>
249+
*/
250+
protected function reformatHeaders(array $headers): array
251+
{
252+
$result = [];
253+
254+
foreach ($headers as $header) {
255+
[$key, $value] = explode(": ", $header, 2);
256+
$result[$key] = [$value];
257+
}
258+
259+
return $result;
260+
}
159261
}

src/OpenIDConnectServiceProvider.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected function registerConfigurationLoader(): void
8787
$app['config']->get('oidc.issuer'),
8888
$app['cache']->store($app['config']->get('oidc.configuration_cache.store')),
8989
$app['config']->get('oidc.configuration_cache.ttl'),
90-
$app['config']->get('oidc.tls_verify') === true,
90+
$app['config']->get('oidc.tls_verify'),
9191
);
9292
});
9393
}
@@ -104,18 +104,17 @@ protected function registerClient(): void
104104
if (!empty($app['config']->get('oidc.client_secret'))) {
105105
$oidc->setClientSecret($app['config']->get('oidc.client_secret'));
106106
}
107-
$oidc->setCodeChallengeMethod($app['config']->get('oidc.code_challenge_method'));
107+
if (!empty($app['config']->get('oidc.code_challenge_method'))) {
108+
$oidc->setCodeChallengeMethod($app['config']->get('oidc.code_challenge_method'));
109+
}
108110
$oidc->setRedirectURL($app['url']->route('oidc.login'));
109111

110112
$additionalScopes = $app['config']->get('oidc.additional_scopes');
111113
if (is_array($additionalScopes) && count($additionalScopes) > 0) {
112114
$oidc->addScope($additionalScopes);
113115
}
114116

115-
if ($app['config']->get('oidc.tls_verify') !== true) {
116-
$oidc->setVerifyHost(false);
117-
$oidc->setVerifyPeer(false);
118-
}
117+
$oidc->setTlsVerify($app['config']->get('oidc.tls_verify'));
119118
return $oidc;
120119
});
121120
}

0 commit comments

Comments
 (0)