Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down
28 changes: 19 additions & 9 deletions lib/Command/DeleteProvider.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down Expand Up @@ -30,7 +31,7 @@ public function __construct(
parent::__construct();
}

protected function configure() {
protected function configure(): void {
$this
->setName('user_oidc:provider:delete')
->setDescription('Delete an OpenId connect provider')
Expand All @@ -39,26 +40,35 @@ protected function configure() {
parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output) {
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$identifier = $input->getArgument('identifier');

try {
$provider = $this->providerMapper->findProviderByIdentifier($identifier);
} catch (DoesNotExistException $e) {
$output->writeln('Provider not found.');
return -1;
}

$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Are you sure you want to delete OpenID Provider "' . $provider->getIdentifier() . '"? It may invalidate all associated user accounts [y/N] ', false);
if ($input->getOption('force') || $helper->ask($input, $output, $question)) {
$this->providerMapper->delete($provider);
$this->providerService->deleteSettings($provider->getId());
$output->writeln('"' . $provider->getIdentifier() . '" has been deleted.');
$question = new ConfirmationQuestion(
'Are you sure you want to delete OpenID Provider "' . $provider->getIdentifier() . '"? It may invalidate all associated user accounts [y/N] ',
false
);

if (!$input->getOption('force') && !$helper->ask($input, $output, $question)) {
return 0;
}

$this->providerMapper->delete($provider);
$this->providerService->deleteSettings($provider->getId());
$output->writeln('<info>"' . $provider->getIdentifier() . '" has been deleted.</info>');

return 0;
} catch (Exception $e) {
$output->writeln($e->getMessage());
$output->writeln('<error>' . $e->getMessage() . '</error>');
return -1;
}
return 0;
}
}
32 changes: 15 additions & 17 deletions lib/Command/ListProviders.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand All @@ -26,7 +27,7 @@ public function __construct(
parent::__construct();
}

protected function configure() {
protected function configure(): void {
$this
->setName('user_oidc:providers')
->setDescription('List all providers and print their configuration')
Expand All @@ -35,7 +36,7 @@ protected function configure() {
parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output) {
protected function execute(InputInterface $input, OutputInterface $output): int {
$outputFormat = $input->getOption('output') ?? 'json_pretty';
$sensitive = $input->getOption('sensitive');

Expand All @@ -44,42 +45,39 @@ protected function execute(InputInterface $input, OutputInterface $output) {
$providersWithSettings = array_map(function ($provider) use ($sensitive) {
$providerSettings = $this->providerService->getSettings($provider->getId());
$serializedProvider = $provider->jsonSerialize();

if ($sensitive) {
$serializedProvider['clientId'] = '********';
$serializedProvider['clientSecret'] = '********';
try {
$discoveryDomainName = parse_url($serializedProvider['discoveryEndpoint'], PHP_URL_HOST);
$serializedProvider['discoveryEndpoint'] = str_replace($discoveryDomainName, '********', $serializedProvider['discoveryEndpoint']);
} catch (\Exception|\Throwable) {
// Failed to parse URL, keep original endpoint
}
} else {
$serializedProvider['clientSecret'] = $this->crypto->decrypt($provider->getClientSecret());
}

return array_merge($serializedProvider, ['settings' => $providerSettings]);
}, $providers);

if ($outputFormat === 'json') {
foreach ($providersWithSettings as $provider) {
$output->writeln(json_encode($provider, JSON_THROW_ON_ERROR));
}
return 0;
}
$jsonFlags = match ($outputFormat) {
'json' => JSON_THROW_ON_ERROR,
'json_pretty' => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT,
default => null,
};

if ($outputFormat === 'json_pretty') {
if ($jsonFlags !== null) {
foreach ($providersWithSettings as $provider) {
$output->writeln(json_encode($provider, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
$output->writeln(json_encode($provider, $jsonFlags));
}
return 0;
}

$output->writeln(
'<comment>Only "' . self::OUTPUT_FORMAT_JSON . '" and "' . self::OUTPUT_FORMAT_JSON_PRETTY . '" output formats are supported.</comment>',
);
$output->writeln('<comment>Only "' . self::OUTPUT_FORMAT_JSON . '" and "' . self::OUTPUT_FORMAT_JSON_PRETTY . '" output formats are supported.</comment>');
$output->writeln('<comment>Use "--output=' . self::OUTPUT_FORMAT_JSON . '" or "--output=' . self::OUTPUT_FORMAT_JSON_PRETTY . '" (default format is "' . self::OUTPUT_FORMAT_JSON_PRETTY . '")</comment>');

$output->writeln(
'<comment>Use "--output=' . self::OUTPUT_FORMAT_JSON . '" or "--output=' . self::OUTPUT_FORMAT_JSON_PRETTY . '" '
. '(default format is "' . self::OUTPUT_FORMAT_JSON_PRETTY . '")</comment>',
);
return 0;
}
}
146 changes: 90 additions & 56 deletions lib/Command/UpsertProvider.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
Expand Down Expand Up @@ -171,7 +172,7 @@ public function __construct(
parent::__construct();
}

protected function configure() {
protected function configure(): void {
$this
->setName('user_oidc:provider')
->setDescription('Create, show or update a OpenId connect provider config given the identifier of a provider')
Expand All @@ -188,10 +189,14 @@ protected function configure() {
parent::configure();
}

protected function execute(InputInterface $input, OutputInterface $output) {
protected function execute(InputInterface $input, OutputInterface $output): int {
$outputFormat = $input->getOption('output') ?? 'table';

$identifier = $input->getArgument('identifier');

if ($identifier === null) {
return $this->listProviders($input, $output);
}

$clientid = $input->getOption('clientid');
$clientsecret = $input->getOption('clientsecret');
if ($clientsecret !== null) {
Expand All @@ -202,108 +207,137 @@ protected function execute(InputInterface $input, OutputInterface $output) {
$postLogoutUri = $input->getOption('postlogouturi');
$scope = $input->getOption('scope');

if ($identifier === null) {
return $this->listProviders($input, $output);
}

// check if any option for updating is provided
// Check if any option for updating is provided
$updateOptions = array_filter($input->getOptions(), static function ($value, $option) {
return in_array($option, [
'identifier', 'clientid', 'clientsecret', 'discoveryuri', 'endsessionendpointuri', 'postlogouturi', 'scope',
...array_keys(self::EXTRA_OPTIONS),
]) && $value !== null;
], true) && $value !== null;
}, ARRAY_FILTER_USE_BOTH);

if (count($updateOptions) === 0) {
try {
$provider = $this->providerMapper->findProviderByIdentifier($identifier);
} catch (DoesNotExistException $e) {
$output->writeln('Provider not found');
return -1;
}
$provider = $this->providerService->getProviderWithSettings($provider->getId());
if ($outputFormat === 'json') {
$output->writeln(json_encode($provider, JSON_THROW_ON_ERROR));
return 0;
}
return $this->displayProvider($identifier, $outputFormat, $output);
}

if ($outputFormat === 'json_pretty') {
$output->writeln(json_encode($provider, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
return 0;
}
return $this->updateOrCreateProvider($input, $output, $identifier, $clientid, $clientsecret, $discoveryuri, $scope, $endsessionendpointuri, $postLogoutUri);
}

private function displayProvider(string $identifier, string $outputFormat, OutputInterface $output): int {
try {
$provider = $this->providerMapper->findProviderByIdentifier($identifier);
} catch (DoesNotExistException) {
$output->writeln('<error>Provider not found</error>');
return 1;
}

$provider = $this->providerService->getProviderWithSettings($provider->getId());

$provider['settings'] = json_encode($provider['settings']);
$table = new Table($output);
$table->setHeaders(['ID', 'Identifier', 'Client ID', 'Discovery endpoint', 'End session endpoint', 'Advanced settings']);
$table->addRow($provider);
$table->render();
$jsonFlags = match ($outputFormat) {
'json' => JSON_THROW_ON_ERROR,
'json_pretty' => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT,
default => null,
};

if ($jsonFlags !== null) {
$output->writeln(json_encode($provider, $jsonFlags));
return 0;
}

$provider['settings'] = json_encode($provider['settings']);
$table = new Table($output);
$table->setHeaders(['ID', 'Identifier', 'Client ID', 'Discovery endpoint', 'End session endpoint', 'Advanced settings']);
$table->addRow($provider);
$table->render();

return 0;
}

private function updateOrCreateProvider(
InputInterface $input,
OutputInterface $output,
string $identifier,
?string $clientid,
?string $clientsecret,
?string $discoveryuri,
?string $scope,
?string $endsessionendpointuri,
?string $postLogoutUri,
): int {
$provider = $this->providerService->getProviderByIdentifier($identifier);

if ($provider !== null) {
// existing provider, keep values that are not set, the scope has to be set anyway
$scope = $scope ?? $provider->getScope();
// Existing provider, keep values that are not set, the scope has to be set anyway
$scope ??= $provider->getScope();
} else {
// new provider default scope value
$scope = $scope ?? 'openid email profile';
// New provider default scope value
$scope ??= 'openid email profile';
}

try {
$provider = $this->providerMapper->createOrUpdateProvider(
$identifier, $clientid, $clientsecret, $discoveryuri, $scope, $endsessionendpointuri, $postLogoutUri
);
// invalidate JWKS cache (even if it was just created)

// Invalidate JWKS cache (even if it was just created)
$this->providerService->setSetting($provider->getId(), ProviderService::SETTING_JWKS_CACHE, '');
$this->providerService->setSetting($provider->getId(), ProviderService::SETTING_JWKS_CACHE_TIMESTAMP, '');
} catch (DoesNotExistException|MultipleObjectsReturnedException $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
return -1;
return 1;
}

foreach (self::EXTRA_OPTIONS as $name => $option) {
if (($value = $input->getOption($name)) !== null) {
if (array_key_exists($option['setting_key'], ProviderService::BOOLEAN_SETTINGS_DEFAULT_VALUES)) {
$value = (string)$value === '0' ? '0' : '1';
} else {
$value = (string)$value;
}
$this->providerService->setSetting($provider->getId(), $option['setting_key'], $value);
$value = $input->getOption($name);
if ($value === null) {
continue;
}

if (array_key_exists($option['setting_key'], ProviderService::BOOLEAN_SETTINGS_DEFAULT_VALUES)) {
$value = (string)$value === '0' ? '0' : '1';
} else {
$value = (string)$value;
}

$this->providerService->setSetting($provider->getId(), $option['setting_key'], $value);
}

return 0;
}

private function listProviders(InputInterface $input, OutputInterface $output) {
private function listProviders(InputInterface $input, OutputInterface $output): int {
$outputFormat = $input->getOption('output') ?? 'table';
$providers = $this->providerMapper->getProviders();

if ($outputFormat === 'json') {
$output->writeln(json_encode($providers, JSON_THROW_ON_ERROR));
if (count($providers) === 0) {
$output->writeln('<comment>No providers configured</comment>');
return 0;
}

if ($outputFormat === 'json_pretty') {
$output->writeln(json_encode($providers, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
return 0;
}
$jsonFlags = match ($outputFormat) {
'json' => JSON_THROW_ON_ERROR,
'json_pretty' => JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT,
default => null,
};

if (count($providers) === 0) {
$output->writeln('No providers configured');
if ($jsonFlags !== null) {
$output->writeln(json_encode($providers, $jsonFlags));
return 0;
}

$table = new Table($output);
$table->setHeaders(['ID', 'Identifier', 'Discovery endpoint', 'End session endpoint', 'Client ID']);
$providers = array_map(function ($provider) {
return [
$table->setRows(array_map(
fn ($provider) => [
$provider->getId(),
$provider->getIdentifier(),
$provider->getDiscoveryEndpoint(),
$provider->getEndSessionEndpoint(),
$provider->getClientId()
];
}, $providers);
$table->setRows($providers);
],
$providers
));
$table->render();

return 0;
}
}
Loading
Loading