diff --git a/appinfo/app.php b/appinfo/app.php
deleted file mode 100644
index 507897707..000000000
--- a/appinfo/app.php
+++ /dev/null
@@ -1,176 +0,0 @@
-getURLGenerator();
- $l = \OC::$server->getL10N('user_saml');
- $config = \OC::$server->getConfig();
- $request = \OC::$server->getRequest();
- $userSession = \OC::$server->getUserSession();
- $session = \OC::$server->getSession();
-} catch (Throwable $e) {
- $logger = \OCP\Server::get(LoggerInterface::class);
- $logger->critical($e->getMessage(), ['exception' => $e, 'app' => 'user_saml']);
- return;
-}
-
-$groupBackend = \OC::$server->get(GroupBackend::class);
-\OC::$server->get(IGroupManager::class)->addBackend($groupBackend);
-
-$samlSettings = \OC::$server->get(SAMLSettings::class);
-
-$userBackend = \OCP\Server::get(UserBackend::class);
-$userBackend->registerBackends(\OC::$server->getUserManager()->getBackends());
-OC_User::useBackend($userBackend);
-
-$params = [];
-
-// Setting up the one login config may fail, if so, do not catch the requests later.
-$returnScript = false;
-$type = '';
-switch ($config->getAppValue('user_saml', 'type')) {
- case 'saml':
- $type = 'saml';
- break;
- case 'environment-variable':
- $type = 'environment-variable';
- break;
- default:
- return;
-}
-
-if ($type === 'environment-variable') {
- // We should ignore oauth2 token endpoint (oauth can send the credentials as basic auth which will fail with apache auth)
- $uri = $request->getRequestUri();
- if (substr($uri, -24) === '/apps/oauth/api/v1/token') {
- return;
- }
-
- try {
- OC_User::handleApacheAuth();
- } catch (LoginException $e) {
- if ($request->getPathInfo() === '/apps/user_saml/saml/error') {
- return;
- }
- $targetUrl = $urlGenerator->linkToRouteAbsolute(
- 'user_saml.SAML.genericError',
- [
- 'message' => $e->getMessage()
- ]
- );
- header('Location: ' . $targetUrl);
- exit();
- }
-}
-
-if ($returnScript === true) {
- return;
-}
-
-$app = \OC::$server->query(\OCA\User_SAML\AppInfo\Application::class);
-$app->registerDavAuth();
-
-$redirectSituation = false;
-
-$user = $userSession->getUser();
-if ($user !== null) {
- $enabled = $user->isEnabled();
- if ($enabled === false) {
- if ($request->getPathInfo() === '/apps/user_saml/saml/error') {
- return;
- }
- $targetUrl = $urlGenerator->linkToRouteAbsolute(
- 'user_saml.SAML.genericError',
- [
- 'message' => $l->t('This user account is disabled, please contact your administrator.')
- ]
- );
- header('Location: ' . $targetUrl);
- exit();
- }
-}
-
-// All requests that are not authenticated and match against the "/login" route are
-// redirected to the SAML login endpoint
-if (!$cli &&
- !$userSession->isLoggedIn() &&
- \OC::$server->getRequest()->getPathInfo() === '/login' &&
- $type !== '') {
- try {
- $params = $request->getParams();
- } catch (\LogicException $e) {
- // ignore exception when PUT is called since getParams cannot parse parameters in that case
- }
- if (isset($params['direct']) && ($params['direct'] === 1 || $params['direct'] === '1')) {
- return;
- }
- $redirectSituation = true;
-}
-
-$multipleUserBackEnds = $samlSettings->allowMultipleUserBackEnds();
-$configuredIdps = $samlSettings->getListOfIdps();
-$showLoginOptions = ($multipleUserBackEnds || count($configuredIdps) > 1) && $type === 'saml';
-
-if ($redirectSituation === true && $showLoginOptions) {
- try {
- $params = $request->getParams();
- } catch (\LogicException $e) {
- // ignore exception when PUT is called since getParams cannot parse parameters in that case
- }
- $redirectUrl = '';
- if (isset($params['redirect_url'])) {
- $redirectUrl = $params['redirect_url'];
- }
-
- $targetUrl = $urlGenerator->linkToRouteAbsolute(
- 'user_saml.SAML.selectUserBackEnd',
- [
- 'redirectUrl' => $redirectUrl
- ]
- );
- header('Location: ' . $targetUrl);
- exit();
-}
-
-if ($redirectSituation === true) {
- try {
- $params = $request->getParams();
- } catch (\LogicException $e) {
- // ignore exception when PUT is called since getParams cannot parse parameters in that case
- }
- $originalUrl = '';
- if (isset($params['redirect_url'])) {
- $originalUrl = $urlGenerator->getAbsoluteURL($params['redirect_url']);
- }
-
- $csrfToken = \OC::$server->getCsrfTokenManager()->getToken();
- $targetUrl = $urlGenerator->linkToRouteAbsolute(
- 'user_saml.SAML.login',
- [
- 'requesttoken' => $csrfToken->getEncryptedValue(),
- 'originalUrl' => $originalUrl,
- 'idp' => array_keys($configuredIdps)[0] ?? '',
- ]
- );
- header('Location: ' . $targetUrl);
- exit();
-}
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0dd691cfd..2225101ec 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -26,6 +26,7 @@ While theoretically any other authentication provider implementing either one of
User_SAML
+
https://portal.nextcloud.com/article/configuring-single-sign-on-10.html
@@ -60,4 +61,9 @@ While theoretically any other authentication provider implementing either one of
OCA\User_SAML\Settings\Admin
OCA\User_SAML\Settings\Section
+
+
+ OCA\User_SAML\DavPlugin
+
+
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index c9e517e91..150f395a2 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -1,5 +1,7 @@
getContainer();
-
- /**
- * Middleware
- */
- $container->registerService('OnlyLoggedInMiddleware', function (ContainerInterface $c) {
- return new OnlyLoggedInMiddleware(
- $c->get(IControllerMethodReflector::class),
- $c->get(IUserSession::class),
- $c->get(IURLGenerator::class)
- );
- });
+ }
- $container->registerService(DavPlugin::class, function (ContainerInterface $c) {
+ public function register(IRegistrationContext $context): void {
+ $context->registerMiddleware(OnlyLoggedInMiddleware::class);
+ $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadAdditionalScriptsListener::class);
+ $context->registerService(DavPlugin::class, function (ContainerInterface $c) {
return new DavPlugin(
$c->get(ISession::class),
$c->get(IConfig::class),
@@ -47,36 +58,164 @@ public function __construct(array $urlParams = []) {
$c->get(SAMLSettings::class)
);
});
-
- $container->registerMiddleWare('OnlyLoggedInMiddleware');
- $this->timezoneHandling();
}
- public function registerDavAuth(): void {
- $dispatcher = Server::get(IEventDispatcher::class);
- $dispatcher->addListener('OCA\DAV\Connector\Sabre::addPlugin', function (SabrePluginEvent $event) {
- $event->getServer()->addPlugin(Server::get(DavPlugin::class));
- });
- }
+ public function boot(IBootContext $context): void {
+ try {
+ $context->injectFn(function (
+ IL10N $l10n,
+ IURLGenerator $urlGenerator,
+ IConfig $config,
+ IRequest $request,
+ IUserSession $userSession,
+ ISession $session,
+ IFactory $factory,
+ SAMLSettings $samlSettings,
+ IUserManager $userManager,
+ IDBConnection $connection,
+ LoggerInterface $logger,
+ GroupManager $groupManager,
+ IEventDispatcher $dispatcher,
+ CsrfTokenManager $csrfTokenManager,
+ bool $isCLI,
+ ) {
+ $groupBackend = Server::get(GroupBackend::class);
+ Server::get(IGroupManager::class)->addBackend($groupBackend);
- private function timezoneHandling(): void {
- $userSession = Server::get(IUserSession::class);
- $session = Server::get(ISession::class);
- $config = Server::get(IConfig::class);
+ $samlSettings = Server::get(SAMLSettings::class);
- $dispatcher = Server::get(IEventDispatcher::class);
- $dispatcher->addListener(LoadAdditionalScriptsEvent::class, function () use ($session, $config, $userSession) {
- if (!$userSession->isLoggedIn()) {
- return;
- }
+ $userBackend = Server::get(UserBackend::class);
- $user = $userSession->getUser();
- $timezoneDB = $config->getUserValue($user->getUID(), 'core', 'timezone', '');
+ $userBackend->registerBackends($userManager->getBackends());
+ OC_User::useBackend($userBackend);
- if ($timezoneDB === '' || !$session->exists('timezone')) {
- Util::addScript('user_saml', 'vendor/jstz.min');
- Util::addScript('user_saml', 'timezone');
- }
- });
+ $params = [];
+
+ // Setting up the one login config may fail, if so, do not catch the requests later.
+ switch ($config->getAppValue('user_saml', 'type')) {
+ case 'saml':
+ $type = 'saml';
+ break;
+ case 'environment-variable':
+ $type = 'environment-variable';
+ break;
+ default:
+ return;
+ }
+
+ if ($type === 'environment-variable') {
+ // We should ignore oauth2 token endpoint (oauth can send the credentials as basic auth which will fail with apache auth)
+ $uri = $request->getRequestUri();
+ if (str_ends_with($uri, '/apps/oauth/api/v1/token')) {
+ return;
+ }
+
+ try {
+ OC_User::handleApacheAuth();
+ } catch (LoginException $e) {
+ if ($request->getPathInfo() === '/apps/user_saml/saml/error') {
+ return;
+ }
+ $targetUrl = $urlGenerator->linkToRouteAbsolute(
+ 'user_saml.SAML.genericError',
+ [
+ 'message' => $e->getMessage()
+ ]
+ );
+ header('Location: ' . $targetUrl);
+ exit();
+ }
+ }
+
+ $redirectSituation = false;
+
+ $user = $userSession->getUser();
+ if ($user !== null) {
+ $enabled = $user->isEnabled();
+ if ($enabled === false) {
+ if ($request->getPathInfo() === '/apps/user_saml/saml/error') {
+ return;
+ }
+ $targetUrl = $urlGenerator->linkToRouteAbsolute(
+ 'user_saml.SAML.genericError',
+ [
+ 'message' => $l10n->t('This user account is disabled, please contact your administrator.')
+ ]
+ );
+ header('Location: ' . $targetUrl);
+ exit();
+ }
+ }
+
+ // All requests that are not authenticated and match against the "/login" route are
+ // redirected to the SAML login endpoint
+ if (!$isCLI &&
+ !$userSession->isLoggedIn() &&
+ ($request->getPathInfo() === '/login')) {
+ try {
+ $params = $request->getParams();
+ } catch (\LogicException $e) {
+ // ignore exception when PUT is called since getParams cannot parse parameters in that case
+ }
+ if (isset($params['direct']) && ($params['direct'] === 1 || $params['direct'] === '1')) {
+ return;
+ }
+ $redirectSituation = true;
+ }
+
+ $multipleUserBackEnds = $samlSettings->allowMultipleUserBackEnds();
+ $configuredIdps = $samlSettings->getListOfIdps();
+ $showLoginOptions = $multipleUserBackEnds || count($configuredIdps) > 1;
+
+ if ($redirectSituation === true && $showLoginOptions) {
+ try {
+ $params = $request->getParams();
+ } catch (\LogicException $e) {
+ // ignore exception when PUT is called since getParams cannot parse parameters in that case
+ }
+ $redirectUrl = '';
+ if (isset($params['redirect_url'])) {
+ $redirectUrl = $params['redirect_url'];
+ }
+
+ $targetUrl = $urlGenerator->linkToRouteAbsolute(
+ 'user_saml.SAML.selectUserBackEnd',
+ [
+ 'redirectUrl' => $redirectUrl
+ ]
+ );
+ header('Location: ' . $targetUrl);
+ exit();
+ }
+
+ if ($redirectSituation === true) {
+ try {
+ $params = $request->getParams();
+ } catch (\LogicException $e) {
+ // ignore exception when PUT is called since getParams cannot parse parameters in that case
+ }
+ $originalUrl = '';
+ if (isset($params['redirect_url'])) {
+ $originalUrl = $urlGenerator->getAbsoluteURL($params['redirect_url']);
+ }
+
+ $csrfToken = $csrfTokenManager->getToken();
+ $targetUrl = $urlGenerator->linkToRouteAbsolute(
+ 'user_saml.SAML.login',
+ [
+ 'requesttoken' => $csrfToken->getEncryptedValue(),
+ 'originalUrl' => $originalUrl,
+ 'idp' => array_keys($configuredIdps)[0] ?? '',
+ ]
+ );
+ header('Location: ' . $targetUrl);
+ exit();
+ }
+ });
+ } catch (Throwable $e) {
+ Server::get(LoggerInterface::class)->critical('Error when loading user_saml app', [
+ 'exception' => $e,
+ ]);
+ }
}
}
diff --git a/lib/Listener/LoadAdditionalScriptsListener.php b/lib/Listener/LoadAdditionalScriptsListener.php
new file mode 100644
index 000000000..dd5b29331
--- /dev/null
+++ b/lib/Listener/LoadAdditionalScriptsListener.php
@@ -0,0 +1,46 @@
+ */
+class LoadAdditionalScriptsListener implements IEventListener {
+ public function __construct(
+ private ISession $session,
+ private IUserSession $userSession,
+ private IConfig $config,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof BeforeTemplateRenderedEvent) {
+ return;
+ }
+
+ if (!$event->isLoggedIn()) {
+ return;
+ }
+
+ $user = $this->userSession->getUser();
+ $timezoneDB = $this->config->getUserValue($user->getUID(), 'core', 'timezone', '');
+
+ if ($timezoneDB === '' || !$this->session->exists('timezone')) {
+ Util::addScript('user_saml', 'vendor/jstz.min');
+ Util::addScript('user_saml', 'timezone');
+ }
+ }
+}
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
index 9772c2300..dd0a1ef0d 100644
--- a/tests/psalm-baseline.xml
+++ b/tests/psalm-baseline.xml
@@ -5,19 +5,10 @@
-->
-
-
-
-
-
-
-
- getServer()]]>
-
@@ -36,12 +27,7 @@
-
-
-
-
-
@@ -91,7 +77,6 @@
-
diff --git a/tests/stub.phpstub b/tests/stub.phpstub
index 8cfa75fd9..04451c801 100644
--- a/tests/stub.phpstub
+++ b/tests/stub.phpstub
@@ -52,3 +52,24 @@ namespace OC\DB\Exceptions {
namespace OCP\Log {
public function logger(?string $appId = null): \Psr\Log\LoggerInterface;
}
+
+namespace OC\Security\CSRF {
+ class CsrfToken {
+ public function getEncryptedValue(): string {
+ return 'token';
+ }
+ }
+ class CsrfTokenManager {
+ abstract public function getToken(): CsrfToken;
+ }
+}
+
+namespace OC\User {
+ class LoginException extends \Exception {
+ }
+}
+
+class OC_User {
+ public static function useBackend($userBackend): void;
+ public static function handleApacheAuth(): void;
+}
diff --git a/tests/unit/AppInfo/ApplicationTest.php b/tests/unit/AppInfo/ApplicationTest.php
index 2398354ac..db394e555 100644
--- a/tests/unit/AppInfo/ApplicationTest.php
+++ b/tests/unit/AppInfo/ApplicationTest.php
@@ -28,7 +28,7 @@ public function testContainerAppName() {
public function queryData() {
return [
- ['OnlyLoggedInMiddleware', OnlyLoggedInMiddleware::class],
+ [OnlyLoggedInMiddleware::class],
];
}
@@ -37,10 +37,7 @@ public function queryData() {
* @param string $service
* @param string $expected
*/
- public function testContainerQuery($service, $expected = null) {
- if ($expected === null) {
- $expected = $service;
- }
- $this->assertTrue($this->container->query($service) instanceof $expected);
+ public function testContainerQuery($serviceClass) {
+ $this->assertTrue($this->container->query($serviceClass) instanceof $serviceClass);
}
}