Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions apps/dav/lib/Connector/Sabre/PublicAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,19 @@ public function check(RequestInterface $request, ResponseInterface $response): a
try {
$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), self::BRUTEFORCE_ACTION);

if (count($_COOKIE) > 0 && !$this->request->passesStrictCookieCheck() && $this->getShare()->isPasswordProtected()) {
throw new PreconditionFailed('Strict cookie check failed');
}

$auth = new HTTP\Auth\Basic(
$this->realm,
$request,
$response
);

$userpass = $auth->getCredentials();

// the check targets ambient session replay, supplied credentials are not ambient
if ($userpass === null && count($_COOKIE) > 0 && !$this->request->passesStrictCookieCheck() && $this->getShare()->isPasswordProtected()) {
throw new PreconditionFailed('Strict cookie check failed');
}

// If authentication provided, checking its validity
if ($userpass && !$this->validateUserPass($userpass[0], $userpass[1])) {
return [false, 'Username or password was incorrect'];
Expand Down Expand Up @@ -144,7 +146,12 @@ private function checkToken(): array {
// If the share is protected but user is not authenticated
if ($share->isPasswordProtected()) {
$this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
throw new NotAuthenticated();
if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) {
// do not challenge over ajax, it would trigger the browser password prompt
throw new NotAuthenticated();
}
// only a returned failure reaches Auth\Plugin::challenge()
return [false, 'No password supplied for password protected share'];
}

return [true, $this->principalPrefix . $token];
Expand Down
90 changes: 88 additions & 2 deletions apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ public function testCheckTokenAlreadyAuthenticated(): void {
public function testCheckTokenPasswordNotAuthenticated(): void {
$this->request->method('getPathInfo')
->willReturn('/dav/files/GX9HSGQrGE');
$this->request->method('getHeader')->with('X-Requested-With')->willReturn('');

$share = $this->createMock(IShare::class);
$share->method('getPassword')->willReturn('password');
$share->method('isPasswordProtected')->willReturn(true);
$share->method('getShareType')->willReturn(42);

$this->shareManager->expects($this->once())
->method('getShareByToken')
->with('GX9HSGQrGE')
->willReturn($share);

$this->session->method('exists')->with('public_link_authenticated')->willReturn(false);

$result = self::invokePrivate($this->auth, 'checkToken');
$this->assertSame([false, 'No password supplied for password protected share'], $result);
}

public function testCheckTokenPasswordNotAuthenticatedAjax(): void {
$this->request->method('getPathInfo')
->willReturn('/dav/files/GX9HSGQrGE');
$this->request->method('getHeader')->with('X-Requested-With')->willReturn('XMLHttpRequest');

$share = $this->createMock(IShare::class);
$share->method('getPassword')->willReturn('password');
Expand All @@ -164,6 +186,7 @@ public function testCheckTokenPasswordNotAuthenticated(): void {
public function testCheckTokenPasswordAuthenticatedWrongShare(): void {
$this->request->method('getPathInfo')
->willReturn('/dav/files/GX9HSGQrGE');
$this->request->method('getHeader')->with('X-Requested-With')->willReturn('');

$share = $this->createMock(IShare::class);
$share->method('getPassword')->willReturn('password');
Expand All @@ -178,8 +201,71 @@ public function testCheckTokenPasswordAuthenticatedWrongShare(): void {
$this->session->method('exists')->with('public_link_authenticated')->willReturn(false);
$this->session->method('get')->with('public_link_authenticated')->willReturn('43');

$this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class);
self::invokePrivate($this->auth, 'checkToken');
$result = self::invokePrivate($this->auth, 'checkToken');
$this->assertSame([false, 'No password supplied for password protected share'], $result);
}

public function testCheckWithoutCredentialsRunsStrictCookieCheck(): void {
$this->request->method('getPathInfo')
->willReturn('/dav/files/GX9HSGQrGE');
$this->request->method('passesStrictCookieCheck')->willReturn(false);

$share = $this->createMock(IShare::class);
$share->method('isPasswordProtected')->willReturn(true);

$this->shareManager->method('getShareByToken')
->with('GX9HSGQrGE')
->willReturn($share);

$this->urlGenerator->method('linkToRoute')->willReturn('/s/GX9HSGQrGE');

$cookies = $_COOKIE;
$_COOKIE = ['nc_session_id' => 'irrelevant'];

try {
$this->expectException(\Sabre\DAV\Exception\PreconditionFailed::class);
$this->auth->check(
new \Sabre\HTTP\Request('PROPFIND', '/public.php/dav/files/GX9HSGQrGE'),
new \Sabre\HTTP\Response(),
);
} finally {
$_COOKIE = $cookies;
}
}

public function testCheckWithCredentialsSkipsStrictCookieCheck(): void {
$this->request->method('getPathInfo')
->willReturn('/dav/files/GX9HSGQrGE');
$this->request->method('passesStrictCookieCheck')->willReturn(false);

$share = $this->createMock(IShare::class);
$share->method('isPasswordProtected')->willReturn(true);
$share->method('getShareType')->willReturn(IShare::TYPE_LINK);
$share->method('getId')->willReturn('42');

$this->shareManager->method('getShareByToken')
->with('GX9HSGQrGE')
->willReturn($share);
$this->shareManager->method('checkPassword')
->with($share, 'password')
->willReturn(true);

$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn(['42']);

$request = new \Sabre\HTTP\Request('PROPFIND', '/public.php/dav/files/GX9HSGQrGE');
$request->addHeader('Authorization', 'Basic ' . base64_encode('GX9HSGQrGE:password'));

$cookies = $_COOKIE;
$_COOKIE = ['nc_session_id' => 'irrelevant'];

try {
$result = $this->auth->check($request, new \Sabre\HTTP\Response());
} finally {
$_COOKIE = $cookies;
}

$this->assertSame([true, 'principals/GX9HSGQrGE'], $result);
}

public function testNoShare(): void {
Expand Down
Loading