Skip to content

Commit

Permalink
Merge #13 Central customization setup V31
Browse files Browse the repository at this point in the history
  • Loading branch information
memurats committed Jan 14, 2025
2 parents a744cc8 + ae86fd7 commit f5fba4d
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 8 deletions.
56 changes: 56 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions COPYING.DTAG
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 6 additions & 2 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
['name' => 'login#singleLogoutService', 'url' => '/sls', 'verb' => 'GET'],
['name' => 'login#backChannelLogout', 'url' => '/backchannel-logout/{providerIdentifier}', 'verb' => 'POST'],

['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'],
['name' => 'api#deleteUser', 'url' => '/user/{userId}', 'verb' => 'DELETE'],
// 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 have to disable the endpoint
// ['name' => 'api#createUser', 'url' => '/user', 'verb' => 'POST'],
// ['name' => 'api#deleteUser', 'url' => '/user/{userId}', 'verb' => 'DELETE'],

['name' => 'id4me#showLogin', 'url' => '/id4me', 'verb' => 'GET'],
['name' => 'id4me#login', 'url' => '/id4me', 'verb' => 'POST'],
Expand Down
84 changes: 78 additions & 6 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@
use OCA\UserOIDC\Event\ExchangedTokenRequestedEvent;
use OCA\UserOIDC\Listener\ExchangedTokenRequestedListener;
use OCA\UserOIDC\Listener\TimezoneHandlingListener;
use OCA\UserOIDC\MagentaBearer\MBackend;
use OCA\UserOIDC\Service\ID4MeService;
use OCA\UserOIDC\Service\ProvisioningEventService;
use OCA\UserOIDC\Service\ProvisioningService;
use OCA\UserOIDC\Service\SettingsService;
use OCA\UserOIDC\Service\TokenService;
use OCA\UserOIDC\User\Backend;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Security\ISecureRandom;

// this is needed only for the special, shortened client login flow
use Psr\Container\ContainerInterface;
use Throwable;

class Application extends App implements IBootstrap {
Expand All @@ -43,14 +49,23 @@ 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);
});

/** @var IUserManager $userManager */
$userManager = $this->getContainer()->get(IUserManager::class);

/* Register our own user backend */
$this->backend = $this->getContainer()->get(Backend::class);
// $this->backend = $this->getContainer()->get(Backend::class);
$this->backend = $this->getContainer()->get(MBackend::class);
// this was done before but OC_User::useBackend calls OC::$server->getUserManager()->registerBackend anyway
// so the backend was registered twice, leading to wrong user count (double)
// $userManager->registerBackend($this->backend);
$userManager->registerBackend($this->backend);
// TODO check if it can be replaced by $userManager->registerBackend($this->backend); in our case
OC_User::useBackend($this->backend);

Expand All @@ -70,12 +85,69 @@ 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) {
}
}

private function checkLoginToken(TokenService $tokenService): void {
$tokenService->checkLoginToken();
/**
* 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 {
Expand Down
1 change: 1 addition & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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();

0 comments on commit f5fba4d

Please sign in to comment.