aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Calviño Sánchez <danxuliu@gmail.com>2025-06-05 04:25:34 +0200
committerDaniel Calviño Sánchez <danxuliu@gmail.com>2025-07-11 12:01:04 +0200
commitc6f870245abac40e7fa39d3a8cff8dfb26dcd298 (patch)
tree188a2d2790c3b20d49bc9d1fde635c87e9f63888
parente8e41ccf405182e59ca7449334705551a56c990c (diff)
downloadnextcloud-server-backport/53501/stable30.tar.gz
nextcloud-server-backport/53501/stable30.zip
fix: Fix theming for disabled accountsbackport/53501/stable30
The Theming app injects the stylesheets for the different themes in the "<header>" element of the page, and those stylesheets are then loaded by the browser from a "Controller" (a plain "Controller", not an "OCSController"). The stylesheets, in turn, may also get some images (like the background) also from the "Controller". When handling a request to "index.php" it is checked whether the user is logged in and, if not, a login is tried. A disabled user is explicitly seen as not logged in, so a login is always tried in that case, but disabled users are also explicitly prevented to log in, so the login also fails. Due to that trying to get any of the themed stylesheets or images with a disabled account (to be able to show the "Account disabled" error page) fails with an HTTP status 401. To solve that, and to avoid touching this basic logic as much as possible, the login exception is now ignored (if the user is disabled) for some specific requests to the Theming app. The clouds.jpg file was not available in stable30, so the file and its license were copied from the commit that introduced them in newer branches, 19ce3628965f73f21beac3fc9ee3757e091313c4. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
-rw-r--r--.github/workflows/integration-sqlite.yml1
-rw-r--r--build/integration/config/behat.yml10
-rw-r--r--build/integration/data/clouds.jpgbin0 -> 538205 bytes
-rw-r--r--build/integration/data/clouds.jpg.license2
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php1
-rw-r--r--build/integration/features/bootstrap/Theming.php49
-rw-r--r--build/integration/theming_features/theming.feature131
-rw-r--r--lib/base.php23
8 files changed, 216 insertions, 1 deletions
diff --git a/.github/workflows/integration-sqlite.yml b/.github/workflows/integration-sqlite.yml
index fa566a870fe..ccba980c49c 100644
--- a/.github/workflows/integration-sqlite.yml
+++ b/.github/workflows/integration-sqlite.yml
@@ -68,6 +68,7 @@ jobs:
- 'setup_features'
- 'sharees_features'
- 'sharing_features'
+ - 'theming_features'
- 'videoverification_features'
php-versions: ['8.1']
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 192dc973045..d6c1e1aa7da 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -245,3 +245,13 @@ default:
- admin
- admin
regular_user_password: 123456
+ theming:
+ paths:
+ - "%paths.base%/../theming_features"
+ contexts:
+ - FeatureContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
diff --git a/build/integration/data/clouds.jpg b/build/integration/data/clouds.jpg
new file mode 100644
index 00000000000..2433b140766
--- /dev/null
+++ b/build/integration/data/clouds.jpg
Binary files differ
diff --git a/build/integration/data/clouds.jpg.license b/build/integration/data/clouds.jpg.license
new file mode 100644
index 00000000000..d7c54c39d02
--- /dev/null
+++ b/build/integration/data/clouds.jpg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2019 CHUTTERSNAP <https://unsplash.com/@chuttersnap> <https://unsplash.com/photos/blue-clouds-under-white-sky-9AqIdzEc9pY>"
+SPDX-License-Identifier: LicenseRef-Unsplash
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index 5c7e6e0a595..66f108f8af9 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -19,6 +19,7 @@ trait BasicStructure {
use Avatar;
use Download;
use Mail;
+ use Theming;
/** @var string */
private $currentUser = '';
diff --git a/build/integration/features/bootstrap/Theming.php b/build/integration/features/bootstrap/Theming.php
new file mode 100644
index 00000000000..f44a6533a1b
--- /dev/null
+++ b/build/integration/features/bootstrap/Theming.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait Theming {
+
+ private bool $undoAllThemingChangesAfterScenario = false;
+
+ /**
+ * @AfterScenario
+ */
+ public function undoAllThemingChanges() {
+ if (!$this->undoAllThemingChangesAfterScenario) {
+ return;
+ }
+
+ $this->loggingInUsingWebAs('admin');
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/undoAllChanges');
+
+ $this->undoAllThemingChangesAfterScenario = false;
+ }
+
+ /**
+ * @When logged in admin uploads theming image for :key from file :source
+ *
+ * @param string $key
+ * @param string $source
+ */
+ public function loggedInAdminUploadsThemingImageForFromFile(string $key, string $source) {
+ $this->undoAllThemingChangesAfterScenario = true;
+
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
+
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/uploadImage?key=' . $key,
+ [
+ 'multipart' => [
+ [
+ 'name' => 'image',
+ 'contents' => $file
+ ]
+ ]
+ ]);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+}
diff --git a/build/integration/theming_features/theming.feature b/build/integration/theming_features/theming.feature
new file mode 100644
index 00000000000..2ae5d4f75c3
--- /dev/null
+++ b/build/integration/theming_features/theming.feature
@@ -0,0 +1,131 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: theming
+
+ Background:
+ Given user "user0" exists
+
+ Scenario: themed stylesheets are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ # Themes that can not be explicitly set by a guest could have been
+ # globally set too through "enforce_theme".
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for guests
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for disabled users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
diff --git a/lib/base.php b/lib/base.php
index f9aaf887f2f..a0a9801b47f 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -11,6 +11,7 @@ use OC\Share20\GroupDeletedListener;
use OC\Share20\Hooks;
use OC\Share20\UserDeletedListener;
use OC\Share20\UserRemovedListener;
+use OC\User\DisabledUserException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserRemovedEvent;
@@ -996,7 +997,27 @@ class OC {
// OAuth needs to support basic auth too, so the login is not valid
// inside Nextcloud and the Login exception would ruin it.
if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
- self::handleLogin($request);
+ try {
+ self::handleLogin($request);
+ } catch (DisabledUserException $e) {
+ // Disabled users would not be seen as logged in and
+ // trying to log them in would fail, so the login
+ // exception is ignored for the themed stylesheets and
+ // images.
+ if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
+ && $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
+ && $request->getRawPathInfo() !== '/apps/theming/image/background'
+ && $request->getRawPathInfo() !== '/apps/theming/image/logo'
+ && $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
+ && !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
+ && !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
+ throw $e;
+ }
+ }
}
}
}