From c2f00eae3bdb767a1233d595bddc856284223468 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 5 Jul 2023 18:58:10 +0200 Subject: [PATCH 01/15] Add branding restrictions --- COPYING.DTAG | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 COPYING.DTAG diff --git a/COPYING.DTAG b/COPYING.DTAG new file mode 100644 index 00000000..958cd262 --- /dev/null +++ b/COPYING.DTAG @@ -0,0 +1,5 @@ +Although this Nextcloud app code is free and available under the AGPL3 license, Deutsche Telekom +(including T-Systems) fully reserves all rights to the Telekom brand. To prevent users from getting confused about +the source of a digital product or experience, there are stringent restrictions on using the Telekom brand and design, +even when built into code that we provide. For any customization other than explicitly for Telekom or T-Systems, you must +replace the Deutsche Telekom and T-Systems brand elements contained in the provided sources. \ No newline at end of file From c5cbb0ded8355fd4bcf95d4295a34b53918a6c4c Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 12 Jul 2023 12:00:37 +0200 Subject: [PATCH 02/15] Override upstream README.md by own in .github --- .github/README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/README.md diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 00000000..768b7049 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,56 @@ +# MagentaCLOUD user_oidc + +Customisation of the Nextcloud delivered OpenID connect app for MagentaCLOUD. + +The app extends the standard `user_oidc` Nextcloud app, +see [upstream configuration hints for basic setup](https://github.com/nextcloud/user_oidc/blob/main/README.md) + + +## Feature: Event-based provisioning (upstream contribution candidate) +The mechanism allows to implement custom puser provisioning logic in a separate Nextcloud app by +registering and handling a attribute change and provisioning event: + +``` +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\Bootstrap\IRegistrationContext; + +class Application extends App implements IBootstrap { +... + public function register(IRegistrationContext $context): void { + $context->registerEventListener(AttributeMappedEvent::class, MyUserAttributeListener::class); + $context->registerEventListener(UserAccountChangeEvent::class, MyUserAccountChangeListener::class); + } +... +} +``` +The provisioning handler should return a `OCA\UserOIDC\Event\UserAccountChangeResult` object + +## Feature: Telekom-specific bearer token + +Due to historic reason, Telekom bearer tokens have a close to standard structure, but +require special security implementation in detail. The customisation overrides te standard + + +### Requiring web-token libraries +The central configuration branch `nmc/2372-central-setup` automatic merge will frequently fail if composer +upstream + +The fast and easy way to bring it back to sync with upstream is: +``` +git checkout nmc/2372-central-setup +git rebase --onto main nmc/2372-central-setup +# manually take over everything from upstream for composer.lock (TODO: automate that) + +# ALWAYS update web-token dependencies in composer.lock +# to avoid upstream conflicts. The lock file diff should only contain adds to upstream state! +composer update "web-token/jwt-*" +``` + + +### Configuring an additional Bearer preshared secret with provider +TODO + +### Testing Bearer secrets +TODO \ No newline at end of file From 0c8b9194a4b68835a0404321e28dd94b8ac33a8d Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Sat, 19 Aug 2023 09:08:25 +0200 Subject: [PATCH 03/15] Add Magenta namespace for unittests --- tests/bootstrap.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6f46408d..dfe076ba 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,6 +26,7 @@ require_once __DIR__.'/../vendor/autoload.php'; \OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); +\OC::$composerAutoloader->addPsr4('OCA\\UserOIDC\\BaseTest\\', dirname(__FILE__) . '/unit/MagentaCLOUD/', true); \OC_App::loadApp('user_oidc'); OC_Hook::clear(); From 93e2e56a3f750ac90948debccbe3240ef01934ad Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Sun, 27 Aug 2023 19:08:50 +0200 Subject: [PATCH 04/15] Disable api#createUser to avoid dark accounts bypassing provisioning --- appinfo/routes.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 16a08d94..c3c1a607 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,7 +30,8 @@ ['name' => 'login#singleLogoutService', 'url' => '/sls', 'verb' => 'GET'], ['name' => 'login#backChannelLogout', 'url' => '/backchannel-logout/{providerIdentifier}', 'verb' => 'POST'], - ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'], + // this is a security problem combined with Telekom provisioning, so we habe to disable the endpoint + // ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'], ['name' => 'id4me#showLogin', 'url' => '/id4me', 'verb' => 'GET'], ['name' => 'id4me#login', 'url' => '/id4me', 'verb' => 'POST'], From 6eb610fae7047dda71aed06322ce229f37ee2157 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Sun, 27 Aug 2023 19:13:16 +0200 Subject: [PATCH 05/15] Clean code --- appinfo/routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index c3c1a607..9fac948d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -30,7 +30,7 @@ ['name' => 'login#singleLogoutService', 'url' => '/sls', 'verb' => 'GET'], ['name' => 'login#backChannelLogout', 'url' => '/backchannel-logout/{providerIdentifier}', 'verb' => 'POST'], - // this is a security problem combined with Telekom provisioning, so we habe to disable the endpoint + // this is a security problem combined with Telekom provisioning, so we habe to disable the endpoint // ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'], ['name' => 'id4me#showLogin', 'url' => '/id4me', 'verb' => 'GET'], From f4546967df5a1d570b65ed9bb0c911ce0c745422 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 30 Aug 2023 09:09:53 +0200 Subject: [PATCH 06/15] Register new bearer backend --- lib/AppInfo/Application.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 2040025f..503ad16d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -33,7 +33,7 @@ use OCA\UserOIDC\Listener\TimezoneHandlingListener; use OCA\UserOIDC\Service\ID4MeService; use OCA\UserOIDC\Service\SettingsService; -use OCA\UserOIDC\User\Backend; +use OCA\UserOIDC\MagentaBearer\MBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -61,7 +61,7 @@ public function register(IRegistrationContext $context): void { $userManager = $this->getContainer()->get(IUserManager::class); /* Register our own user backend */ - $this->backend = $this->getContainer()->get(Backend::class); + $this->backend = $this->getContainer()->get(MBackend::class); $userManager->registerBackend($this->backend); OC_User::useBackend($this->backend); From 935a2f967e3209c0bcfab076d742c5f331c37378 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Thu, 31 Aug 2023 17:46:10 +0200 Subject: [PATCH 07/15] Fix path typo --- tests/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index dfe076ba..5b1da1c7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,7 +26,7 @@ require_once __DIR__.'/../vendor/autoload.php'; \OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); -\OC::$composerAutoloader->addPsr4('OCA\\UserOIDC\\BaseTest\\', dirname(__FILE__) . '/unit/MagentaCLOUD/', true); +\OC::$composerAutoloader->addPsr4('OCA\\UserOIDC\\BaseTest\\', dirname(__FILE__) . '/unit/MagentaCloud/', true); \OC_App::loadApp('user_oidc'); OC_Hook::clear(); From c6f2ec59aecc6df7dda9be9ebea3ad6b0dad3504 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 1 Sep 2023 13:36:09 +0200 Subject: [PATCH 08/15] Register event-based provisioning solution --- lib/AppInfo/Application.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 503ad16d..214dc4cc 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -33,6 +33,8 @@ use OCA\UserOIDC\Listener\TimezoneHandlingListener; use OCA\UserOIDC\Service\ID4MeService; use OCA\UserOIDC\Service\SettingsService; +use OCA\UserOIDC\Service\ProvisioningService; +use OCA\UserOIDC\Service\ProvisioningEventService; use OCA\UserOIDC\MagentaBearer\MBackend; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -57,7 +59,10 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { - /** @var IUserManager $userManager */ + // override registration of provisioning srevice to use event-based solution + $context->registerServiceAlias(ProvisioningService::class, ProvisioningEventService::class); + + /** @var IUserManager $userManager */ $userManager = $this->getContainer()->get(IUserManager::class); /* Register our own user backend */ From 3035a333bacec463b3bdf4d772754bd44545806a Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 1 Sep 2023 21:44:03 +0200 Subject: [PATCH 09/15] Fix registration to actually override default provisioning --- lib/AppInfo/Application.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 214dc4cc..f2ca41f4 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -46,6 +46,7 @@ use OCP\IUserManager; use OCP\IUserSession; use Throwable; +use Psr\Container\ContainerInterface; class Application extends App implements IBootstrap { public const APP_ID = 'user_oidc'; @@ -60,8 +61,10 @@ public function __construct(array $urlParams = []) { public function register(IRegistrationContext $context): void { // override registration of provisioning srevice to use event-based solution - $context->registerServiceAlias(ProvisioningService::class, ProvisioningEventService::class); - + $this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService { + return $c->get(ProvisioningEventService::class); + }); + /** @var IUserManager $userManager */ $userManager = $this->getContainer()->get(IUserManager::class); From d0feefe727a8ae71c7311d88d4c065672f58adc0 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 1 Sep 2023 21:47:09 +0200 Subject: [PATCH 10/15] Clean up code --- lib/AppInfo/Application.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f2ca41f4..9e99f551 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -60,12 +60,12 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { - // override registration of provisioning srevice to use event-based solution - $this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService { - return $c->get(ProvisioningEventService::class); - }); + // override registration of provisioning srevice to use event-based solution + $this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService { + return $c->get(ProvisioningEventService::class); + }); - /** @var IUserManager $userManager */ + /** @var IUserManager $userManager */ $userManager = $this->getContainer()->get(IUserManager::class); /* Register our own user backend */ From 231ba2ec731e1cbbda560be3201fb4a98e3d17e3 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Thu, 7 Sep 2023 12:21:11 +0200 Subject: [PATCH 11/15] Fix autoload of jwt-token libraries --- lib/AppInfo/Application.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9e99f551..1f39ff07 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -60,6 +60,9 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { + // Register the composer autoloader required for the added jwt-token libs + include_once __DIR__ . '/../../vendor/autoload.php'; + // override registration of provisioning srevice to use event-based solution $this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService { return $c->get(ProvisioningEventService::class); From 908b4d5f13546d3daaadeaa3f7f4c6584b27b131 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Thu, 7 Sep 2023 12:24:54 +0200 Subject: [PATCH 12/15] Clean up code --- lib/AppInfo/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1f39ff07..d191a70a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -60,7 +60,7 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { - // Register the composer autoloader required for the added jwt-token libs + // Register the composer autoloader required for the added jwt-token libs include_once __DIR__ . '/../../vendor/autoload.php'; // override registration of provisioning srevice to use event-based solution From 9854bd9d261303d4eefd7e9a92600bf51f77cf00 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Wed, 27 Sep 2023 11:57:45 +0200 Subject: [PATCH 13/15] Add old endpoint temporary for backchannel logout to allow silent switchover on PROD --- appinfo/routes.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index 9fac948d..18695715 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -29,6 +29,8 @@ ['name' => 'login#code', 'url' => '/code', 'verb' => 'GET'], ['name' => 'login#singleLogoutService', 'url' => '/sls', 'verb' => 'GET'], ['name' => 'login#backChannelLogout', 'url' => '/backchannel-logout/{providerIdentifier}', 'verb' => 'POST'], + // compatibility with NMC V24 until reconfig on SAM + ['name' => 'login#telekomBackChannelLogout', 'url' => '/logout', 'verb' => 'POST'], // this is a security problem combined with Telekom provisioning, so we habe to disable the endpoint // ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'], From 4150f620b85e95738034eec61a0b19ec91b8a511 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Tue, 21 Nov 2023 12:26:26 +0100 Subject: [PATCH 14/15] Customize ClientFlow, skip authpicker, late consent --- lib/AppInfo/Application.php | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d191a70a..bdec72ae 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -48,6 +48,10 @@ use Throwable; use Psr\Container\ContainerInterface; +// this is needed only for the special, shortened client login flow +use OCP\Security\ISecureRandom; +use OCP\ISession; + class Application extends App implements IBootstrap { public const APP_ID = 'user_oidc'; public const OIDC_API_REQ_HEADER = 'Authorization'; @@ -90,10 +94,71 @@ public function boot(IBootContext $context): void { try { $context->injectFn(\Closure::fromCallable([$this, 'registerRedirect'])); $context->injectFn(\Closure::fromCallable([$this, 'registerLogin'])); + // this is the custom auto-redirect for MagentaCLOUD client access + $context->injectFn(\Closure::fromCallable([$this, 'registerNmcClientFlow'])); } catch (Throwable $e) { } } + /** + * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients + * completely skipping consent layer + */ + private function registerNmcClientFlow(IRequest $request, + IURLGenerator $urlGenerator, + ProviderMapper $providerMapper, + ISession $session, + ISecureRandom $random): void { + + $providers = $this->getCachedProviders($providerMapper); + + // Handle immediate redirect on client first-time login + $isClientLoginFlow = false; + try { + $isClientLoginFlow = $request->getPathInfo() === '/login/flow'; + } catch (Exception $e) { + // in case any errors happen when checking for the path do not apply redirect logic as it is only needed for the login + } + if ($isClientLoginFlow) { + // only redirect if Telekom provider registered + $tproviders = array_values(array_filter($providers, function ($p) { + return strtolower($p->getIdentifier()) === "telekom"; + })); + if (count($tproviders) == 0) { + // always show normal login flow as error fallback + return; + } + + $stateToken = $random->generate( + 64, + ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS + ); + $session->set('client.flow.state.token', $stateToken); + + // call the service to get the params, but suppress the template + // compute grant redirect Url to go directly to Telekom login + $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ + 'stateToken' => $stateToken, + // grantPage service operation is deriving oauth2 client name (again), + // so we simply pass on clientIdentifier or empty string + 'clientIdentifier' => $request->getParam('clientIdentifier', ''), + 'direct' => $request->getParam('direct', '0') + ]); + if ($redirectUrl === null) { + // always show normal login flow as error fallback + return; + } + + // direct login, consent layer later + $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [ + 'providerId' => $tproviders[0]->getId(), + 'redirectUrl' => $redirectUrl + ]); + header('Location: ' . $targetUrl); + exit(); + } + } + private function registerRedirect(IRequest $request, IURLGenerator $urlGenerator, SettingsService $settings, ProviderMapper $providerMapper): void { $providers = $this->getCachedProviders($providerMapper); $redirectUrl = $request->getParam('redirect_url'); From ae1df93a8898795ae295b785b437e6dbd46fa4b9 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Tue, 21 Nov 2023 15:24:29 +0100 Subject: [PATCH 15/15] Clean up clientflow code --- lib/AppInfo/Application.php | 63 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index bdec72ae..83a6bb69 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -104,13 +104,12 @@ public function boot(IBootContext $context): void { * This is the automatic redirect exclusively for Nextcloud/Magentacloud clients * completely skipping consent layer */ - private function registerNmcClientFlow(IRequest $request, - IURLGenerator $urlGenerator, - ProviderMapper $providerMapper, - ISession $session, - ISecureRandom $random): void { - - $providers = $this->getCachedProviders($providerMapper); + private function registerNmcClientFlow(IRequest $request, + IURLGenerator $urlGenerator, + ProviderMapper $providerMapper, + ISession $session, + ISecureRandom $random): void { + $providers = $this->getCachedProviders($providerMapper); // Handle immediate redirect on client first-time login $isClientLoginFlow = false; @@ -125,33 +124,33 @@ private function registerNmcClientFlow(IRequest $request, return strtolower($p->getIdentifier()) === "telekom"; })); if (count($tproviders) == 0) { - // always show normal login flow as error fallback - return; - } - - $stateToken = $random->generate( - 64, - ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS - ); - $session->set('client.flow.state.token', $stateToken); + // always show normal login flow as error fallback + return; + } + + $stateToken = $random->generate( + 64, + ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS + ); + $session->set('client.flow.state.token', $stateToken); - // call the service to get the params, but suppress the template - // compute grant redirect Url to go directly to Telekom login - $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ - 'stateToken' => $stateToken, - // grantPage service operation is deriving oauth2 client name (again), - // so we simply pass on clientIdentifier or empty string - 'clientIdentifier' => $request->getParam('clientIdentifier', ''), - 'direct' => $request->getParam('direct', '0') - ]); - if ($redirectUrl === null) { - // always show normal login flow as error fallback - return; - } - - // direct login, consent layer later + // call the service to get the params, but suppress the template + // compute grant redirect Url to go directly to Telekom login + $redirectUrl = $urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', [ + 'stateToken' => $stateToken, + // grantPage service operation is deriving oauth2 client name (again), + // so we simply pass on clientIdentifier or empty string + 'clientIdentifier' => $request->getParam('clientIdentifier', ''), + 'direct' => $request->getParam('direct', '0') + ]); + if ($redirectUrl === null) { + // always show normal login flow as error fallback + return; + } + + // direct login, consent layer later $targetUrl = $urlGenerator->linkToRoute(self::APP_ID . '.login.login', [ - 'providerId' => $tproviders[0]->getId(), + 'providerId' => $tproviders[0]->getId(), 'redirectUrl' => $redirectUrl ]); header('Location: ' . $targetUrl);