aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-05-06 20:17:24 +0200
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>2024-08-08 12:34:24 +0000
commite7547a9c9b9f1b1b061ca5b623d92c4d9f18a13a (patch)
treec275d0686d903eca90fd302a7123fbbcab983e45
parent598d50164a918c452fff2f3f6f81f26fdde8071a (diff)
downloadnextcloud-server-e7547a9c9b9f1b1b061ca5b623d92c4d9f18a13a.tar.gz
nextcloud-server-e7547a9c9b9f1b1b061ca5b623d92c4d9f18a13a.zip
fix(dav): Try basic auth for ajax WebDAV requests
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r--apps/dav/lib/Connector/Sabre/Auth.php12
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/AuthTest.php93
2 files changed, 72 insertions, 33 deletions
diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php
index 694aad09c80..3fb5852cd96 100644
--- a/apps/dav/lib/Connector/Sabre/Auth.php
+++ b/apps/dav/lib/Connector/Sabre/Auth.php
@@ -221,18 +221,16 @@ class Auth extends AbstractBasic {
}
}
- if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {
- // do not re-authenticate over ajax, use dummy auth name to prevent browser popup
- $response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
- $response->setStatus(401);
- throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
- }
-
$data = parent::check($request, $response);
if ($data[0] === true) {
$startPos = strrpos($data[1], '/') + 1;
$user = $this->userSession->getUser()->getUID();
$data[1] = substr_replace($data[1], $user, $startPos);
+ } elseif (in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {
+ // For ajax requests use dummy auth name to prevent browser popup in case of invalid creditials
+ $response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
+ $response->setStatus(401);
+ throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
}
return $data;
}
diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
index 06559261f3c..296e7e0836b 100644
--- a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
@@ -35,6 +35,7 @@ use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use OCP\Security\Bruteforce\IThrottler;
+use PHPUnit\Framework\MockObject\MockObject;
use Sabre\DAV\Server;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
@@ -47,17 +48,17 @@ use Test\TestCase;
* @group DB
*/
class AuthTest extends TestCase {
- /** @var ISession */
+ /** @var ISession&MockObject */
private $session;
/** @var \OCA\DAV\Connector\Sabre\Auth */
private $auth;
- /** @var Session */
+ /** @var Session&MockObject */
private $userSession;
- /** @var IRequest */
+ /** @var IRequest&MockObject */
private $request;
- /** @var Manager */
+ /** @var Manager&MockObject */
private $twoFactorManager;
- /** @var IThrottler */
+ /** @var IThrottler&MockObject */
private $throttler;
protected function setUp(): void {
@@ -549,11 +550,11 @@ class AuthTest extends TestCase {
$this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class);
$this->expectExceptionMessage('Cannot authenticate over ajax calls');
- /** @var \Sabre\HTTP\RequestInterface $httpRequest */
+ /** @var \Sabre\HTTP\RequestInterface&MockObject $httpRequest */
$httpRequest = $this->getMockBuilder(RequestInterface::class)
->disableOriginalConstructor()
->getMock();
- /** @var \Sabre\HTTP\ResponseInterface $httpResponse */
+ /** @var \Sabre\HTTP\ResponseInterface&MockObject $httpResponse */
$httpResponse = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
@@ -562,10 +563,59 @@ class AuthTest extends TestCase {
->method('isLoggedIn')
->willReturn(false);
$httpRequest
+ ->expects($this->exactly(2))
+ ->method('getHeader')
+ ->willReturnMap([
+ ['X-Requested-With', 'XMLHttpRequest'],
+ ['Authorization', null],
+ ]);
+
+ $this->auth->check($httpRequest, $httpResponse);
+ }
+
+ public function testAuthenticateWithBasicAuthenticateHeadersProvidedWithAjax(): void {
+ // No CSRF
+ $this->request
->expects($this->once())
+ ->method('passesCSRFCheck')
+ ->willReturn(false);
+
+ /** @var \Sabre\HTTP\RequestInterface&MockObject $httpRequest */
+ $httpRequest = $this->getMockBuilder(RequestInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ /** @var \Sabre\HTTP\ResponseInterface&MockObject $httpResponse */
+ $httpResponse = $this->getMockBuilder(ResponseInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $httpRequest
+ ->expects($this->any())
->method('getHeader')
- ->with('X-Requested-With')
- ->willReturn('XMLHttpRequest');
+ ->willReturnMap([
+ ['X-Requested-With', 'XMLHttpRequest'],
+ ['Authorization', 'basic dXNlcm5hbWU6cGFzc3dvcmQ='],
+ ]);
+
+ $user = $this->getMockBuilder(IUser::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('MyDavUser');
+ $this->userSession
+ ->expects($this->any())
+ ->method('isLoggedIn')
+ ->willReturn(false);
+ $this->userSession
+ ->expects($this->once())
+ ->method('logClientIn')
+ ->with('username', 'password')
+ ->willReturn(true);
+ $this->userSession
+ ->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+
$this->auth->check($httpRequest, $httpResponse);
}
@@ -619,16 +669,11 @@ class AuthTest extends TestCase {
->disableOriginalConstructor()
->getMock();
$server->httpRequest
- ->expects($this->exactly(2))
+ ->expects($this->once())
->method('getHeader')
- ->withConsecutive(
- ['X-Requested-With'],
- ['Authorization'],
- )
- ->willReturnOnConsecutiveCalls(
- null,
- 'basic dXNlcm5hbWU6cGFzc3dvcmQ=',
- );
+ ->with('Authorization')
+ ->willReturn('basic dXNlcm5hbWU6cGFzc3dvcmQ=');
+
$server->httpResponse = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
@@ -661,14 +706,10 @@ class AuthTest extends TestCase {
$server->httpRequest
->expects($this->exactly(2))
->method('getHeader')
- ->withConsecutive(
- ['X-Requested-With'],
- ['Authorization'],
- )
- ->willReturnOnConsecutiveCalls(
- null,
- 'basic dXNlcm5hbWU6cGFzc3dvcmQ=',
- );
+ ->willReturnMap([
+ ['Authorization', 'basic dXNlcm5hbWU6cGFzc3dvcmQ='],
+ ['X-Requested-With', null],
+ ]);
$server->httpResponse = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();