Skip to content

Commit 1ba5cf0

Browse files
committed
fix: avoid TypeError in when session user is null
Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
1 parent 8ea3fcc commit 1ba5cf0

3 files changed

Lines changed: 118 additions & 6 deletions

File tree

lib/Listener/UserLoggedInListener.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ public function handle(Event $event): void {
3030
return;
3131
}
3232

33-
$this->restrictionManager->verifyAccess();
34-
$this->restrictionManager->setupRestrictions();
33+
// Use the user from the event rather than the user session: when the
34+
// event is dispatched from the user_saml ACS handler the user is not
35+
// yet bound to the session, so IUserSession::getUser() returns null
36+
// (see https://github.com/nextcloud/guests/issues/1588).
37+
$user = $event->getUser();
38+
$this->restrictionManager->verifyAccess($user);
39+
$this->restrictionManager->setupRestrictions($user);
3540
}
3641
}

lib/RestrictionManager.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,23 @@ public function __construct(
3838
) {
3939
}
4040

41-
public function verifyAccess(): void {
42-
$this->whitelist->verifyAccess($this->userSession->getUser(), $this->request);
41+
public function verifyAccess(?IUser $user = null): void {
42+
$user ??= $this->userSession->getUser();
43+
44+
if ($user === null) {
45+
// Nothing to verify without a user. The session is not always
46+
// populated when this runs (e.g. the user_saml ACS handler
47+
// dispatches UserLoggedInEvent before binding the user to the
48+
// session), so callers should pass the user explicitly; guard
49+
// here to avoid forwarding null to the non-nullable whitelist.
50+
return;
51+
}
52+
53+
$this->whitelist->verifyAccess($user, $this->request);
4354
}
4455

45-
public function setupRestrictions(): void {
46-
$user = $this->userSession->getUser();
56+
public function setupRestrictions(?IUser $user = null): void {
57+
$user ??= $this->userSession->getUser();
4758

4859
if ($user === null) {
4960
// No user logged in, no restrictions needed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Guests\Test\Unit;
10+
11+
use OCA\Guests\AppWhitelist;
12+
use OCA\Guests\Config;
13+
use OCA\Guests\GuestManager;
14+
use OCA\Guests\RestrictionManager;
15+
use OCA\Guests\UserBackend;
16+
use OCP\Files\Config\IMountProviderCollection;
17+
use OCP\IRequest;
18+
use OCP\IServerContainer;
19+
use OCP\IUser;
20+
use OCP\IUserSession;
21+
use PHPUnit\Framework\MockObject\MockObject;
22+
use Psr\Log\LoggerInterface;
23+
use Test\TestCase;
24+
25+
class RestrictionManagerTest extends TestCase {
26+
private AppWhitelist&MockObject $whitelist;
27+
28+
private IRequest&MockObject $request;
29+
30+
private IUserSession&MockObject $userSession;
31+
32+
private RestrictionManager $restrictionManager;
33+
34+
protected function setUp(): void {
35+
parent::setUp();
36+
37+
$this->whitelist = $this->createMock(AppWhitelist::class);
38+
$this->request = $this->createMock(IRequest::class);
39+
$this->userSession = $this->createMock(IUserSession::class);
40+
41+
$this->restrictionManager = new RestrictionManager(
42+
$this->whitelist,
43+
$this->request,
44+
$this->userSession,
45+
$this->createMock(IServerContainer::class),
46+
$this->createMock(GuestManager::class),
47+
$this->createMock(IMountProviderCollection::class),
48+
$this->createMock(Config::class),
49+
$this->createMock(UserBackend::class),
50+
$this->createMock(LoggerInterface::class),
51+
);
52+
}
53+
54+
/**
55+
* Regression test for https://github.com/nextcloud/guests/issues/1588:
56+
* when the UserLoggedInEvent is dispatched from the user_saml ACS handler
57+
* the user is not yet bound to the session, so falling back to
58+
* IUserSession::getUser() yields null. verifyAccess() must not forward that
59+
* null to AppWhitelist::verifyAccess() (which is typed as non-nullable).
60+
*/
61+
public function testVerifyAccessIsSkippedWhenNoUser(): void {
62+
$this->userSession->method('getUser')
63+
->willReturn(null);
64+
65+
$this->whitelist->expects($this->never())
66+
->method('verifyAccess');
67+
68+
$this->restrictionManager->verifyAccess();
69+
}
70+
71+
public function testVerifyAccessUsesSessionUserWhenNoArgumentGiven(): void {
72+
$user = $this->createMock(IUser::class);
73+
$this->userSession->method('getUser')
74+
->willReturn($user);
75+
76+
$this->whitelist->expects($this->once())
77+
->method('verifyAccess')
78+
->with($user, $this->request);
79+
80+
$this->restrictionManager->verifyAccess();
81+
}
82+
83+
public function testVerifyAccessPrefersExplicitUserOverSession(): void {
84+
// The listener passes the user from the event; the session getUser()
85+
// must not even be consulted in that case.
86+
$eventUser = $this->createMock(IUser::class);
87+
$this->userSession->expects($this->never())
88+
->method('getUser');
89+
90+
$this->whitelist->expects($this->once())
91+
->method('verifyAccess')
92+
->with($eventUser, $this->request);
93+
94+
$this->restrictionManager->verifyAccess($eventUser);
95+
}
96+
}

0 commit comments

Comments
 (0)