From 98c43d98c95c10430db9b38db43b9eeca0a7a014 Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:22:00 +0200 Subject: [PATCH 1/3] fix(auth): keep remember-me cookies pointing at a live session token fix(auth): keep remember-me cookies pointing at a live session token The remember-me cookie outlives the session token it refers to in two cases, and cookie login then fails on every request. A session token created as DO_NOT_REMEMBER (user_oidc, Apache login) is removed by the cleanup job after session_lifetime, while the cookies last remember_login_cookie_lifetime. loginWithApache() now creates its token as REMEMBER, and createRememberMeToken() marks a DO_NOT_REMEMBER token as REMEMBER for other callers with the same mismatch. ISession::regenerateId(true, true) moves the token to the new session id but left nc_session_id pointing at the old one (password protected share unlock, Talk password rooms). The cookie is now rewritten with the new id. Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- lib/private/Session/Internal.php | 2 ++ lib/private/User/Session.php | 15 +++++++++++++++ tests/lib/User/SessionTest.php | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php index 5949471965bca..373dce6c027a2 100644 --- a/lib/private/Session/Internal.php +++ b/lib/private/Session/Internal.php @@ -11,6 +11,7 @@ use OC\Authentication\Token\IProvider; use OC\Diagnostics\TLogSlowOperation; +use OC\User\Session as UserSession; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Session\Exceptions\SessionNotAvailableException; use Psr\Log\LoggerInterface; @@ -140,6 +141,7 @@ public function regenerateId(bool $deleteOldSession = true, bool $updateToken = try { $tokenProvider->renewSessionToken($oldId, $newId); + Server::get(UserSession::class)->renewMagicSessionId($oldId); } catch (InvalidTokenException $e) { // Just ignore } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index b97db3e407b75..1b075e1e6358c 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -1054,6 +1054,21 @@ public function setMagicInCookie($username, $token) { } } + /** + * Point the remember-me cookie at the regenerated session id, so cookie + * login can still find the token that was renewed along with it. + */ + public function renewMagicSessionId(string $oldSessionId): void { + $request = Server::get(IRequest::class); + $username = $request->getCookie('nc_username'); + $token = $request->getCookie('nc_token'); + $sessionId = $request->getCookie('nc_session_id'); + if ($username === null || $token === null || $sessionId !== $oldSessionId) { + return; + } + $this->setMagicInCookie($username, $token); + } + /** * Remove cookie for "remember username" */ diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php index 74121f10f4eef..d0ec8d6191611 100644 --- a/tests/lib/User/SessionTest.php +++ b/tests/lib/User/SessionTest.php @@ -1147,6 +1147,27 @@ public function testCreateRememberMeToken(): void { $this->userSession->createRememberMeToken($user); } + public static function renewMagicSessionIdData(): array { + return [ + 'cookie holds the old session id' => [['nc_username' => 'u', 'nc_token' => 't', 'nc_session_id' => 'old'], true], + 'cookie holds another session id' => [['nc_username' => 'u', 'nc_token' => 't', 'nc_session_id' => 'other'], false], + 'no remember-me cookies' => [[], false], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'renewMagicSessionIdData')] + public function testRenewMagicSessionId(array $cookies, bool $expectRenewal): void { + $this->userSession->expects($expectRenewal ? $this->once() : $this->never()) + ->method('setMagicInCookie') + ->with('u', 't'); + + $request = $this->createMock(IRequest::class); + $request->method('getCookie')->willReturnCallback(fn (string $key) => $cookies[$key] ?? null); + $this->overwriteService(IRequest::class, $request); + + $this->userSession->renewMagicSessionId('old'); + } + public function testTryBasicAuthLoginValid(): void { $request = $this->createMock(Request::class); $request->method('__get') From f9399ae78aa592c0fe9fb08fadc1cdaa5a33c1c0 Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:32:16 +0200 Subject: [PATCH 2/3] fix(auth): create the Apache login session token as remembered loginWithApache() sets remember-me cookies but created the session token as DO_NOT_REMEMBER, so the cleanup job removed it after session_lifetime while the cookie stayed valid, matching the mismatch fixed for the share-unlock case in the previous commit. Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- lib/private/legacy/OC_User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php index f72b9a8958bc8..6aa9678107acf 100644 --- a/lib/private/legacy/OC_User.php +++ b/lib/private/legacy/OC_User.php @@ -163,7 +163,7 @@ public static function loginWithApache(\OCP\Authentication\IApacheBackend $backe /** @var IEventDispatcher $dispatcher */ $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend)); - $userSession->createSessionToken($request, $uid, $uid, $password); + $userSession->createSessionToken($request, $uid, $uid, $password, IToken::REMEMBER); $userSession->createRememberMeToken($userSession->getUser()); if (empty($password)) { From f21fdf52d406f54133da33428c1cd9b770adba8d Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:53:30 +0200 Subject: [PATCH 3/3] fix(auth): fully qualify OCP\Server on stable33 This file has no `use OCP\Server;` import here, unlike master, so the bare `Server::get()` added by the backport resolved to the file's own namespace and failed psalm with UndefinedClass. Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- lib/private/Session/Internal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php index 373dce6c027a2..a7646cd3bb0e8 100644 --- a/lib/private/Session/Internal.php +++ b/lib/private/Session/Internal.php @@ -141,7 +141,7 @@ public function regenerateId(bool $deleteOldSession = true, bool $updateToken = try { $tokenProvider->renewSessionToken($oldId, $newId); - Server::get(UserSession::class)->renewMagicSessionId($oldId); + \OCP\Server::get(UserSession::class)->renewMagicSessionId($oldId); } catch (InvalidTokenException $e) { // Just ignore }