Skip to content

Commit

Permalink
/user/logout endpoint implemented
Browse files Browse the repository at this point in the history
It provides best possible but still ugly from the user perspective
logout option for the HTTP Basic authentication.

Later on this should be moved to the auth library.
  • Loading branch information
zozlak committed Oct 16, 2024
1 parent 295bd49 commit 1877042
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 18 deletions.
11 changes: 11 additions & 0 deletions src/acdhOeaw/arche/core/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,15 @@ public function denyAccess(array $allowed): void {
}
throw new RepoException('Forbidden', 403);
}

public function logout(string $redirectUrl = ''): void {
// the right way would be to have a $this->controller->logout()
unset($_SERVER['PHP_AUTH_USER'], $_SERVER['HTTP_AUTHORIZATION'], $_SERVER['AUTHORIZATION']);
$this->controller->advertise();

if (!empty($redirectUrl)) {
header("Refresh: 0; url=$redirectUrl");
}
throw new RepoException('Logged out', 401);
}
}
30 changes: 16 additions & 14 deletions src/acdhOeaw/arche/core/UserApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,21 @@ public function put(string $user): void {
}

public function get(string $user): void {
if (RC::$auth->isPublic() || !RC::$auth->isAdmin() && !empty($user) && $user !== RC::$auth->getUserName()) {
if (RC::$auth->isPublic() || !RC::$auth->isAdmin() && !empty($user) && $user !== RC::$auth->getUserName() && $user !== 'logout') {
RC::$auth->denyAccess([$user]);
}


$redirect = $_GET['redirect'] ?? '';
$redirectRegex = RC::$config->rest->userEndpointAllowedRedirectRegex ?? '/^$/';
if (!empty($redirect)) {
if (preg_match($redirectRegex, $redirect)) {
RC::setHeader('Location', $redirect);
http_response_code(303);
} else {
throw new RepoException('Redirect location not allowed', 400);
}
}

if (empty($user)) {
$where = '';
$param = [];
Expand All @@ -86,20 +97,11 @@ public function get(string $user): void {
foreach ($users as $user) {
$data[] = $this->prepareUserData($this->db->getUser($user), $user);
}
} else {
} elseif ($user !== 'logout') {
$data = $this->checkUserExists($user);
$data = $this->prepareUserData($data, $user);
}

$redirect = $_GET['redirect'] ?? '';
$redirectRegex = RC::$config->rest->userEndpointAllowedRedirectRegex ?? '/^$/';
if (!empty($redirect)) {
if (preg_match($redirectRegex, $redirect)) {
RC::setHeader('Location', $redirect);
http_response_code(303);
} else {
throw new RepoException('Redirect location not allowed', 400);
}
} else {
RC::$auth->logout($redirect);
}

$data = json_encode($data) ?: throw new \RuntimeException("Can't serialise to JSON");
Expand Down
38 changes: 34 additions & 4 deletions tests/UserApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ public function testUserGet(): void {
$this->assertEquals(['archeLogin=bar%2CpublicRole%2Cacademic; path=/'], $resp->getHeader('Set-Cookie'));

// /user by a non-admin user
$req = new Request('get', self::$baseUrl . 'user', $headers);
$resp = self::$client->send($req);
$req = new Request('get', self::$baseUrl . 'user', $headers);
$resp = self::$client->send($req);
$this->assertEquals(200, $resp->getStatusCode());
$data = json_decode($resp->getBody());
$data = json_decode($resp->getBody());
$this->assertIsArray($data);
$this->assertCount(1, $data);
$data = $data[0];
Expand All @@ -196,7 +196,7 @@ public function testUserGet(): void {
$this->assertFalse(isset($data->password));
$this->assertFalse(isset($data->pswd));
$this->assertEquals(['archeLogin=bar%2CpublicRole%2Cacademic; path=/'], $resp->getHeader('Set-Cookie'));

// lack of priviledges
$headers = ['Authorization' => 'Basic ' . base64_encode('foo:' . self::PSWD)];
$req = new Request('get', self::$baseUrl . 'user/bar', $headers);
Expand Down Expand Up @@ -250,6 +250,36 @@ public function testRedirect(): void {
$this->assertEquals(['archeLogin=bar%2CpublicRole%2Cacademic; path=/'], $resp->getHeader('Set-Cookie'));
}

#[Depends('testUserCreate')]
public function testUserLogout(): void {
$headers = ['Authorization' => 'Basic ' . base64_encode('foo:' . self::PSWD)];

// logout without credentials
$req = new Request('get', self::$baseUrl . 'user/logout?redirect=/foo');
$resp = self::$client->send($req);
$this->assertEquals(401, $resp->getStatusCode());
$this->assertEquals([], $resp->getHeader('Refresh'));

// logout with invalid credentials
$req = new Request('get', self::$baseUrl . 'user/logout?redirect=/foo', [
'Authorization' => 'Basic ' . base64_encode('x:y')]);
$resp = self::$client->send($req);
$this->assertEquals(403, $resp->getStatusCode());
$this->assertEquals([], $resp->getHeader('Refresh'));

// logout with valid credentials
$req = new Request('get', self::$baseUrl . 'user/logout', $headers);
$resp = self::$client->send($req);
$this->assertEquals(401, $resp->getStatusCode());
$this->assertEquals([], $resp->getHeader('Refresh'));

// logout with redirect
$req = new Request('get', self::$baseUrl . 'user/logout?redirect=' . rawurldecode('/foo/bar'), $headers);
$resp = self::$client->send($req);
$this->assertEquals(401, $resp->getStatusCode());
$this->assertEquals(['0; url=/foo/bar'], $resp->getHeader('Refresh'));
}

#[Depends('testUserCreate')]
public function testUserPatch(): void {
// as root
Expand Down

0 comments on commit 1877042

Please sign in to comment.