Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip session validation during app load (Fixes #20756) #20757

Closed
wants to merge 3 commits into from

Conversation

sshambar
Copy link

@sshambar sshambar commented Apr 30, 2020

Adds parameter to User\Session:getUser() to skip validation, and
modifies OC_App::getEnabledApps() to use this parameter to skip
session validation when loading apps.

see #20756 for a detailed explanation

Signed-off-by: Scott Shambarger devel@shambarger.net

fixes #20756
fixes nextcloud/user_external#101
fixes nextcloud/user_external#70
fixes nextcloud/user_external#3

Adds parameter to User\Session:getUser() to skip validation, and
modifies OC_App::getEnabledApps() to use this parameter to skip
session validation when loading apps.

Signed-off-by: Scott Shambarger <devel@shambarger.net>
@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

Thank you very much for looking into that issue 👍

Make sense to me. I'm not sure about the patch yet. A flag to skip the session validation at a certain stage seems a workaround. As you already pointed out that's an issue with the app loading order.

\OC::$server->getUserSession()->getUser() is necessary for getEnabledAppsForUser. The purpose is to load apps enabled for the current user. But it's not possible to limit authentication apps to certain groups hence the authentication apps is always loaded.

That means for loading authentication apps there is no dependency to \OC::$server->getUserSession()->getUser(). Probably some authentication apps itself have a dependency to getUserSession but that's another story.

I think we should introduce a dedicated method to load the authentication apps. Added a small POC but needs more work and testing.

POC
Index: ocs/v1.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- ocs/v1.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ ocs/v1.php	(date 1588510049791)
@@ -51,7 +51,7 @@
  */
 try {
 	OC_App::loadApps(['session']);
-	OC_App::loadApps(['authentication']);
+	OC_App::loadAuthenticationApps();
 	// load all apps to get all api routes properly setup
 	OC_App::loadApps();
 
Index: lib/private/legacy/OC_App.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lib/private/legacy/OC_App.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ lib/private/legacy/OC_App.php	(date 1588510219400)
@@ -94,6 +94,32 @@
 		return in_array($app, self::$loadedApps, true);
 	}
 
+	/**
+	 * Loads all authentication apps
+	 *
+	 * @return bool
+	 */
+	public static function loadAuthenticationApps(): bool {
+		if ((bool)\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
+			return false;
+		}
+
+		$apps = array_filter(\OC::$server->getAppManager()->getInstalledApps(), static function ($app) {
+			return self::isType($app, ['authentication']);
+		});
+
+		// prevent app.php from printing output
+		ob_start();
+		foreach ($apps as $app) {
+			if (!in_array($app, self::$loadedApps, true)) {
+				self::loadApp($app);
+			}
+		}
+		ob_end_clean();
+
+		return true;
+	}
+
 	/**
 	 * loads all apps
 	 *
Index: remote.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- remote.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ remote.php	(date 1588509913770)
@@ -148,7 +148,7 @@
 
 	// Load all required applications
 	\OC::$REQUESTEDAPP = $app;
-	OC_App::loadApps(['authentication']);
+	OC_App::loadAuthenticationApps();
 	OC_App::loadApps(['filesystem', 'logging']);
 
 	switch ($app) {
Index: lib/base.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lib/base.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ lib/base.php	(date 1588509960366)
@@ -982,7 +982,7 @@
 		}
 
 		// Always load authentication apps
-		OC_App::loadApps(['authentication']);
+		OC_App::loadAuthenticationApps();
 
 		// Load minimum set of apps
 		if (!\OCP\Util::needUpgrade()
Index: lib/private/Updater.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lib/private/Updater.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ lib/private/Updater.php	(date 1588509974014)
@@ -262,7 +262,7 @@
 		$this->upgradeAppStoreApps($autoDisabledApps, true);
 
 		// install new shipped apps on upgrade
-		OC_App::loadApps(['authentication']);
+		OC_App::loadAuthenticationApps();
 		$errors = Installer::installShippedApps(true);
 		foreach ($errors as $appId => $exception) {
 			/** @var \Exception $exception */
Index: public.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- public.php	(revision 29b4246840f298c03983457c99f4aa181f644b75)
+++ public.php	(date 1588509946886)
@@ -66,7 +66,7 @@
 
 	// Load all required applications
 	\OC::$REQUESTEDAPP = $app;
-	OC_App::loadApps(['authentication']);
+	OC_App::loadAuthenticationApps();
 	OC_App::loadApps(['filesystem', 'logging']);
 
 	if (!\OC::$server->getAppManager()->isInstalled($app)) {

@sshambar
Copy link
Author

sshambar commented May 3, 2020

Aha, so groups don't apply to authentication apps? That's valuable info... does it apply to prelogin apps too? (as that's the stage when users were logged out in this case :)

I had considered modifying loadApps() to call getEnabledApps() with $all = true for certain app-types, but I didn't know it would be correct logic... if prelogin and authentication app lists can ignore the user, then this would work and be an easy solution (a simple in_array check). (session apps aren't an issue as the user_id isn't available yet, but I think it'd be harmless to add them to the list)

If that's the case, I'd be happy to update the patch...

@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

Aha, so groups don't apply to authentication apps? That's valuable info... does it apply to prelogin apps too? (as that's the stage when users were logged out in this case :)

/**
* Apps with these types can not be enabled for certain groups only
* @var string[]
*/
protected $protectedAppTypes = [
'filesystem',
'prelogin',
'authentication',
'logging',
'prevent_group_restriction',
];

Yep.

If that's the case, I'd be happy to update the patch...

Let's wait for @rullzer and @ChristophWurst ;) The current patch is a rather small change. It's probably easier to get this in now and still do a bigger refactoring later. I think there is already something planed to improve app loading for Nextcloud 20.

@sshambar
Copy link
Author

sshambar commented May 3, 2020

The updated patch probably wouldn't be very large, a new AppManager method (hasNoProtectedAppTypes?), and use that to set the $all flag to getEnabledApps()

@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

prelogin apps

Out of curiosity: Are you still able to reproduce the issue if you remove <prelogin/> from info.xml? (the types are also cached in oc_appconfig table and needs to be updated there too). I've checked user_saml, user_sql and user_ldap and they are not using <prelogin/>. Probably the user_external app is just loaded to early? 🤔 🤷‍♂️

@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

The updated patch probably wouldn't be very large, a new AppManager method (hasNoProtectedAppTypes?), and use that to set the $all flag to getEnabledApps()

Sounds fair but the loadApps method is already complex 💩 😃

@sshambar
Copy link
Author

sshambar commented May 3, 2020

No, the app type isn't the problem (although I agree it's probably unneeded, as it's also an authentication type). The bug is that getUser() is called even before the app-list is fetched in getEnabledApps... and since the DB backend is already loaded, it recognizes the user_id, fails the password check, and invalidates the session.

An alternative solution I considered (but seemed much more invasive), was to add the DB user backend in OC_User::setupBackends(), but that means it's added much later than currently, and might break some code that depends on it.

@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

The bug is that getUser() is called even before the app-list is fetched in getEnabledApps... and since the DB backend is already loaded, it recognizes the user_id, fails the password check, and invalidates the session.

I was just wondering why user_ldap or user_saml are not affected. <prelogin/> seems to be a difference. Also user_external uses a different (probably outdated way) to register user backends. For some reason user_ldap, user_saml and user_sql does this with their appinfo/app.php.

Such an interesting problem 🐛 Thanks again for digging 👍

@sshambar
Copy link
Author

sshambar commented May 3, 2020

Actually, the other apps aren't affected as they use user_id's that are unique, and not likely to be in the database (eg, user_ldap may have a user_id like '3bb4681e-1ed6-103a-9648-7dc9709f72ae', which would be unlikely to overlap with a db user like 'admin').

user_external uses user_id's that match the supplied login, which is likely to overlap with database users... I suppose it could updated to use a "unique" user_id, but it'd create a tricky migration and would likely break a lot of installs.

Still, the underlying logic problem would still exist in the app loading code, where user sessions are validated long before the apps are setup to correctly handle it.

@kesselb
Copy link
Contributor

kesselb commented May 3, 2020

If the user backend is not loaded yet $this->activeUser = $this->manager->get($uid); will be null and validation for the session also skipped.

the underlying logic problem would still exist in the app loading code

Yep (but only for apps using a "outdated" way to register their backend).

@rullzer
Copy link
Member

rullzer commented May 3, 2020

user_external uses user_id's that match the supplied login, which is likely to overlap with database users... I suppose it could updated to use a "unique" user_id, but it'd create a tricky migration and would likely break a lot of installs.

Well that is then the issue. If you have users that are also in the main backend then it should log them out. Or you delete them form the main DB auth. But this not validating seems dangerous.

I really don't wanna mess with the session handling so close to a release.

@sshambar
Copy link
Author

sshambar commented May 3, 2020

Well that is then the issue. If you have users that are also in the main backend then it should log them out. Or you delete them form the main DB auth. But this not validating seems dangerous.

Logging people out when they have a valid login just leads to people thinking nextcloud is broken... and a since there's no logging of why, much confusion (it's not easy to track this problem down, believe me :)

Validating still always happens, just not as early (before supporting apps are loaded)... getUser called everywhere except during the app loading will force a validation (which only happens every 5 mins anyway), and is called many times on any page load.

@sshambar
Copy link
Author

sshambar commented May 3, 2020

Or you delete them form the main DB auth

Would you suggest deleting the admin user? Seems like a very dangerous thing to do...

@sshambar
Copy link
Author

sshambar commented May 3, 2020

Actually, I just gave it a try on my test system.. deleting admin from the database works, and I can still login as admin via user_external. So perhaps that's the workaround for the user_external app until app loading can work without early session invalidation... of course, there's no "backup" login if user_external is every disabled... hmm...

I still like the idea of having getEnableApps() only use getUser() when there's a non-protected app type being loaded... that seems right logically.

@rullzer
Copy link
Member

rullzer commented May 4, 2020

I would even argue that maybe admin is not the best to have as an username ;)

Don't get me wrong we should fix this probably at some point. But just right now I don't wanna touch this so close to 19.

@t2d
Copy link

t2d commented May 4, 2020

Don't get me wrong we should fix this probably at some point. But just right now I don't wanna touch this so close to 19

I don't know how your release process works, so I 'm not qualified to comment on this. But please be aware that #20756 is extremely annoying to big user bases authenticated via IMAP.

@sshambar
Copy link
Author

sshambar commented May 4, 2020

Don't get me wrong we should fix this probably at some point. But just right now I don't wanna touch this so close to 19.

Sounds good, if the release is imminent I can understand not wanting to risk any regressions :)

Unless you think it's a terrible idea, I'll update the patch to create a new "all protected app-types" test to determine if getEnableApps should get the $all=true parameter -- that should always be correct, fixes the problem, and also eliminates any change in user validation.

It might even be more efficient.

)

Adds a new AppManager method which identifies when all app types
are protected, loads all apps (without checking user filters).

This has the intended benefit of avoiding calls to
User\Session:getUser() for these app types, and likely
invalidating the session before user backends are loaded.

Signed-off-by: Scott Shambarger <devel@shambarger.net>
@sshambar
Copy link
Author

sshambar commented May 4, 2020

OK, I've reverted the session invalidation deferral patch, and replaced it with code to request getEnableApps load all apps when only protected app types are being loaded.

This has the intended benefit of avoiding calls to User\Session:getUser() for these app types.

Patch is an alternative, and just as effective, fix to 20756. Please let me know if you'd like me to squish the original patch (I figured for now I'd leave it for reference).

@kesselb
Copy link
Contributor

kesselb commented May 10, 2020

Don't check if the app is enabled for a user for protected apps 👍

But I still think the user_external app should also be fixed:

  • Don't listen for prelogin
  • Don't use user_backends directly. Instead use a different config key (e.g. external_user_backends) and let the app do the backend registration (like user_ldap). I know that's a BC break.

After that we should deprecate usage of user_backends. This will also fix the issue (because the backend is not loaded at the validation stage) and will probably land much sooner.

@Mannshoch

This comment has been minimized.

@blizzz blizzz modified the milestones: Nextcloud 26, Nextcloud 27 Mar 9, 2023
This was referenced May 3, 2023
@blizzz blizzz mentioned this pull request May 17, 2023
@blizzz blizzz modified the milestones: Nextcloud 27, Nextcloud 28 May 23, 2023
@skjnldsv skjnldsv mentioned this pull request Nov 1, 2023
This was referenced Nov 6, 2023
This was referenced Nov 14, 2023
@blizzz blizzz modified the milestones: Nextcloud 28, Nextcloud 29 Nov 23, 2023
@skjnldsv
Copy link
Member

@sshambar have you been able to check @kesselb's comment?
Otherwise, I'll close this PR for inactivity after a bit of time :(

@skjnldsv skjnldsv added 2. developing Work in progress stale Ticket or PR with no recent activity and removed 3. to review Waiting for reviews labels Feb 27, 2024
@skjnldsv skjnldsv marked this pull request as draft February 27, 2024 16:41
@sshambar
Copy link
Author

Don't check if the app is enabled for a user for protected apps 👍

But I still think the user_external app should also be fixed:

I agree that user_external should be fixed.

I suggested using unique userids in user_external to prevent db users from failing validation duing prelogin... but that fix would involve a significant migration step. Wouldn't overlapping userids still be an issue even if the external_user_backends change was made?

Of course, redesigning user_external registration is really a topic that should be discussed with the user_external maintainer (which I'm not :), I was just suggesting a fix to the nextcloud app-reg-flow that would affect any authentication app that had overlapping userids with db users.

This was referenced Mar 12, 2024
@Altahrim Altahrim mentioned this pull request Mar 20, 2024
@skjnldsv skjnldsv modified the milestones: Nextcloud 29, Nextcloud 30 Mar 28, 2024
@skjnldsv skjnldsv closed this Aug 14, 2024
@skjnldsv skjnldsv removed this from the Nextcloud 30 milestone Aug 14, 2024
@skjnldsv
Copy link
Member

Hey @sshambar

It seems you're right, the approach was interesting, but ultimately we need a wider refactor of the user_external registration. Closing then, I hope we can tackle this in a different way in a near future 💪

Thanks for the PR and the discussions @sshambar
I wish you a great day :)
Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2. developing Work in progress bug feature: authentication stale Ticket or PR with no recent activity
Projects
None yet
9 participants