Skip to content

Commit a82a487

Browse files
authored
Merge pull request #1151 from elyerr/feature/fix-env
feat: allow self-signed SSL verification and support for oidc prompt
2 parents a74f8e2 + 06c33ff commit a82a487

9 files changed

Lines changed: 151 additions & 88 deletions

File tree

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@
4040
- Thibault Coupin <thibault.coupin@gmail.com>
4141
- Tobias Wolter <towo@towo.eu>
4242
- Vincent Petry <vincent@nextcloud.com>
43+
- Elvis Yerel Roman Concha <yerel9212@yahoo.es>

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,51 @@ OpenID Connect user backend for Nextcloud
1212
See [Nextcloud and OpenID-Connect](https://web.archive.org/web/20240412121655/https://www.schiessle.org/articles/2023/07/04/nextcloud-and-openid-connect/)
1313
for a proper jumpstart.
1414

15+
---
16+
17+
## `user_oidc.httpclient.allowselfsigned`
18+
19+
```php
20+
'user_oidc' => [
21+
'httpclient.allowselfsigned' => true,
22+
]
23+
```
24+
25+
This configuration allows Nextcloud to **trust self-signed SSL certificates** when making HTTP requests through the internal HTTP client.
26+
It is especially useful when your OAuth2 or OIDC provider is hosted locally or uses a self-signed certificate not recognized by public CAs.
27+
28+
* **true**: Disables SSL certificate verification (adds the `verify => false` option to the actual HTTP client)
29+
* **false** (default): SSL verification remains enabled and strict
30+
31+
> ⚠️ Use with caution in production environments, as disabling certificate verification can introduce security risks.
32+
33+
---
34+
35+
## `user_oidc.prompt`
36+
37+
```php
38+
'user_oidc' => [
39+
'prompt' => 'internal'
40+
]
41+
```
42+
43+
This option allows customizing the `prompt` parameter sent in the OAuth2/OIDC authorization request.
44+
45+
Supported values include:
46+
47+
* `none`
48+
* `login`
49+
* `consent`
50+
* `internal` (custom)
51+
52+
The `internal` prompt is specific to **[OAuth2 Passport Server](https://github.com/elyerr/oauth2-passport-server)** and is designed to enable seamless login
53+
for private or internal applications without requiring user consent or interaction.
54+
55+
Documentation for all supported prompt values is available here:
56+
[Oauth2 passport server prompts-supported](https://gitlab.com/elyerr/oauth2-passport-server/-/wikis/home/prompts-supported)
57+
58+
---
59+
1560
### User IDs
1661

1762
The OpenID Connect backend will ensure that user ids are unique even when multiple providers would report the same user

composer.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/Controller/LoginController.php

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use OCA\UserOIDC\Db\ProviderMapper;
2121
use OCA\UserOIDC\Db\SessionMapper;
2222
use OCA\UserOIDC\Event\TokenObtainedEvent;
23+
use OCA\UserOIDC\Helper\HttpClientHelper;
2324
use OCA\UserOIDC\Service\DiscoveryService;
2425
use OCA\UserOIDC\Service\LdapService;
2526
use OCA\UserOIDC\Service\OIDCService;
@@ -40,7 +41,6 @@
4041
use OCP\Authentication\Token\IToken;
4142
use OCP\DB\Exception;
4243
use OCP\EventDispatcher\IEventDispatcher;
43-
use OCP\Http\Client\IClientService;
4444
use OCP\IConfig;
4545
use OCP\IL10N;
4646
use OCP\IRequest;
@@ -72,7 +72,7 @@ public function __construct(
7272
private LdapService $ldapService,
7373
private ISecureRandom $random,
7474
private ISession $session,
75-
private IClientService $clientService,
75+
private HttpClientHelper $clientService,
7676
private IURLGenerator $urlGenerator,
7777
private IUserSession $userSession,
7878
private IUserManager $userManager,
@@ -260,6 +260,8 @@ public function login(int $providerId, ?string $redirectUrl = null) {
260260
}
261261
}
262262

263+
$oidcConfig = $this->config->getSystemValue('user_oidc', []);
264+
263265
$data += [
264266
'client_id' => $provider->getClientId(),
265267
'response_type' => 'code',
@@ -268,11 +270,16 @@ public function login(int $providerId, ?string $redirectUrl = null) {
268270
'claims' => json_encode($claims),
269271
'state' => $state,
270272
'nonce' => $nonce,
273+
'prompt' => $oidcConfig['prompt'] ?? 'consent'
271274
];
275+
276+
272277
if ($isPkceEnabled) {
273278
$data['code_challenge'] = $this->toCodeChallenge($code_verifier);
274279
$data['code_challenge_method'] = 'S256';
275280
}
281+
282+
276283
$authorizationUrl = $this->discoveryService->buildAuthorizationUrl($discovery['authorization_endpoint'], $data);
277284

278285
$this->logger->debug('Redirecting user to: ' . $authorizationUrl);
@@ -356,7 +363,6 @@ public function code(string $state = '', string $code = '', string $scope = '',
356363
$isPkceSupported = in_array('S256', $discovery['code_challenge_methods_supported'] ?? [], true);
357364
$isPkceEnabled = $isPkceSupported && ($oidcSystemConfig['use_pkce'] ?? true);
358365

359-
$client = $this->clientService->newClient();
360366
try {
361367
$requestBody = [
362368
'code' => $code,
@@ -370,10 +376,12 @@ public function code(string $state = '', string $code = '', string $scope = '',
370376
$headers = [];
371377
$tokenEndpointAuthMethod = 'client_secret_post';
372378
// Use Basic only if client_secret_post is not available as supported by the endpoint
373-
if (array_key_exists('token_endpoint_auth_methods_supported', $discovery) &&
374-
is_array($discovery['token_endpoint_auth_methods_supported']) &&
375-
in_array('client_secret_basic', $discovery['token_endpoint_auth_methods_supported']) &&
376-
!in_array('client_secret_post', $discovery['token_endpoint_auth_methods_supported'])) {
379+
if (
380+
array_key_exists('token_endpoint_auth_methods_supported', $discovery)
381+
&& is_array($discovery['token_endpoint_auth_methods_supported'])
382+
&& in_array('client_secret_basic', $discovery['token_endpoint_auth_methods_supported'])
383+
&& !in_array('client_secret_post', $discovery['token_endpoint_auth_methods_supported'])
384+
) {
377385
$tokenEndpointAuthMethod = 'client_secret_basic';
378386
}
379387

@@ -388,12 +396,10 @@ public function code(string $state = '', string $code = '', string $scope = '',
388396
$requestBody['client_secret'] = $providerClientSecret;
389397
}
390398

391-
$result = $client->post(
399+
$body = $this->clientService->post(
392400
$discovery['token_endpoint'],
393-
[
394-
'body' => $requestBody,
395-
'headers' => $headers,
396-
]
401+
$requestBody,
402+
$headers
397403
);
398404
} catch (ClientException|ServerException $e) {
399405
$response = $e->getResponse();
@@ -417,7 +423,7 @@ public function code(string $state = '', string $code = '', string $scope = '',
417423
return $this->build403TemplateResponse($message, Http::STATUS_FORBIDDEN, [], false);
418424
}
419425

420-
$data = json_decode($result->getBody(), true);
426+
$data = json_decode($body, true);
421427
$this->logger->debug('Received code response: ' . json_encode($data, JSON_THROW_ON_ERROR));
422428
$this->eventDispatcher->dispatchTyped(new TokenObtainedEvent($data, $provider, $discovery));
423429

@@ -676,7 +682,8 @@ public function singleLogoutService() {
676682
$endSessionEndpoint .= '&client_id=' . $provider->getClientId();
677683
$shouldSendIdToken = $this->providerService->getSetting(
678684
$provider->getId(),
679-
ProviderService::SETTING_SEND_ID_TOKEN_HINT, '0'
685+
ProviderService::SETTING_SEND_ID_TOKEN_HINT,
686+
'0'
680687
) === '1';
681688
$idToken = $this->session->get(self::ID_TOKEN);
682689
if ($shouldSendIdToken && $idToken) {
@@ -826,7 +833,9 @@ public function backChannelLogout(string $providerIdentifier, string $logout_tok
826833
* @return JSONResponse
827834
*/
828835
private function getBackchannelLogoutErrorResponse(
829-
string $error, string $description, array $throttleMetadata = [],
836+
string $error,
837+
string $description,
838+
array $throttleMetadata = [],
830839
): JSONResponse {
831840
$this->logger->debug('Backchannel logout error. ' . $error . ' ; ' . $description);
832841
return new JSONResponse(

lib/Helper/HttpClientHelper.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,47 @@
88

99
namespace OCA\UserOIDC\Helper;
1010

11-
use OCP\Http\Client\IClientService;
11+
use OCP\IConfig;
1212

1313
require_once __DIR__ . '/../../vendor/autoload.php';
1414
use Id4me\RP\HttpClient;
15+
use OCP\Http\Client\IClientService;
1516

1617
class HttpClientHelper implements HttpClient {
1718

1819
public function __construct(
1920
private IClientService $clientService,
21+
private IConfig $config,
2022
) {
2123
}
2224

23-
public function get($url, array $headers = []) {
25+
public function get($url, array $headers = [], array $options = []) {
26+
$oidcConfig = $this->config->getSystemValue('user_oidc', []);
27+
2428
$client = $this->clientService->newClient();
2529

26-
return $client->get($url, [
27-
'headers' => $headers,
28-
])->getBody();
30+
if (isset($oidcConfig['httpclient.allowselfsigned'])
31+
&& !in_array($oidcConfig['httpclient.allowselfsigned'], [false, 'false', 0, '0'], true)) {
32+
$options['verify'] = false;
33+
}
34+
35+
return $client->get($url, $options)->getBody();
2936
}
3037

3138
public function post($url, $body, array $headers = []) {
39+
$oidcConfig = $this->config->getSystemValue('user_oidc', []);
3240
$client = $this->clientService->newClient();
3341

34-
return $client->post($url, [
42+
$options = [
3543
'headers' => $headers,
3644
'body' => $body,
37-
])->getBody();
45+
];
46+
47+
if (isset($oidcConfig['httpclient.allowselfsigned'])
48+
&& !in_array($oidcConfig['httpclient.allowselfsigned'], [false, 'false', 0, '0'], true)) {
49+
$options['verify'] = false;
50+
}
51+
52+
return $client->post($url, $options)->getBody();
3853
}
3954
}

lib/Service/DiscoveryService.php

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
namespace OCA\UserOIDC\Service;
1111

1212
use OCA\UserOIDC\Db\Provider;
13+
use OCA\UserOIDC\Helper\HttpClientHelper;
1314
use OCA\UserOIDC\Vendor\Firebase\JWT\JWK;
1415
use OCA\UserOIDC\Vendor\Firebase\JWT\JWT;
15-
use OCP\Http\Client\IClientService;
1616
use OCP\ICache;
1717
use OCP\ICacheFactory;
1818
use Psr\Log\LoggerInterface;
@@ -39,7 +39,7 @@ class DiscoveryService {
3939

4040
public function __construct(
4141
private LoggerInterface $logger,
42-
private IClientService $clientService,
42+
private HttpClientHelper $clientService,
4343
private ProviderService $providerService,
4444
ICacheFactory $cacheFactory,
4545
) {
@@ -53,9 +53,7 @@ public function obtainDiscovery(Provider $provider): array {
5353
$url = $provider->getDiscoveryEndpoint();
5454
$this->logger->debug('Obtaining discovery endpoint: ' . $url);
5555

56-
$client = $this->clientService->newClient();
57-
$response = $client->get($url);
58-
$cachedDiscovery = $response->getBody();
56+
$cachedDiscovery = $this->clientService->get($url);
5957

6058
$this->cache->set($cacheKey, $cachedDiscovery, self::INVALIDATE_DISCOVERY_CACHE_AFTER_SECONDS);
6159
}
@@ -77,8 +75,7 @@ public function obtainJWK(Provider $provider, string $tokenToDecode, bool $useCa
7775
$rawJwks = json_decode($rawJwks, true);
7876
} else {
7977
$discovery = $this->obtainDiscovery($provider);
80-
$client = $this->clientService->newClient();
81-
$responseBody = $client->get($discovery['jwks_uri'])->getBody();
78+
$responseBody = $this->clientService->get($discovery['jwks_uri']);
8279
$rawJwks = json_decode($responseBody, true);
8380
// cache jwks
8481
$this->providerService->setSetting($provider->getId(), ProviderService::SETTING_JWKS_CACHE, $responseBody);
@@ -99,8 +96,8 @@ public function obtainJWK(Provider $provider, string $tokenToDecode, bool $useCa
9996
public function buildAuthorizationUrl(string $authorizationEndpoint, array $extraGetParameters = []): string {
10097
$parsedUrl = parse_url($authorizationEndpoint);
10198

102-
$urlWithoutParams =
103-
(isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : '')
99+
$urlWithoutParams
100+
= (isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : '')
104101
. ($parsedUrl['host'] ?? '')
105102
. (isset($parsedUrl['port']) ? ':' . strval($parsedUrl['port']) : '')
106103
. ($parsedUrl['path'] ?? '');

0 commit comments

Comments
 (0)