1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Enforce admin rquirements in NCNv1

This commit is contained in:
J. King 2021-02-09 10:05:44 -05:00
parent a760bf2ded
commit b7c7915a65
4 changed files with 45 additions and 1 deletions

View file

@ -13,6 +13,10 @@ Bug fixes:
compatibility with RFC 7617 compatibility with RFC 7617
- Accept "t" and "f" as booleans in Tiny Tiny RSS - Accept "t" and "f" as booleans in Tiny Tiny RSS
Changes:
- Administrator account requirements for Nextcloud News functionality are
now enforced
Version 0.8.5 (2020-10-27) Version 0.8.5 (2020-10-27)
========================== ==========================

View file

@ -24,7 +24,6 @@ It allows organizing newsfeeds into single-level folders, and supports a wide ra
- When marking articles as starred the feed ID is ignored, as they are not needed to establish uniqueness - When marking articles as starred the feed ID is ignored, as they are not needed to establish uniqueness
- The feed updater ignores the `userId` parameter: feeds in The Arsse are deduplicated, and have no owner - The feed updater ignores the `userId` parameter: feeds in The Arsse are deduplicated, and have no owner
- The `/feeds/all` route lists only feeds which should be checked for updates, and it also returns all `userId` attributes as empty strings: feeds in The Arsse are deduplicated, and have no owner - The `/feeds/all` route lists only feeds which should be checked for updates, and it also returns all `userId` attributes as empty strings: feeds in The Arsse are deduplicated, and have no owner
- The API's "updater" routes do not require administrator priviledges as The Arsse has no concept of user classes
- The "updater" console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required Nextcloud subsystems - The "updater" console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required Nextcloud subsystems
- The `lastLoginTimestamp` attribute of the user metadata is always the current time: The Arsse's implementation of the protocol is fully stateless - The `lastLoginTimestamp` attribute of the user metadata is always the current time: The Arsse's implementation of the protocol is fully stateless
- Syntactically invalid JSON input will yield a `400 Bad Request` response instead of falling back to GET parameters - Syntactically invalid JSON input will yield a `400 Bad Request` response instead of falling back to GET parameters

View file

@ -360,6 +360,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// return list of feeds which should be refreshed // return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): ResponseInterface { protected function feedListStale(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) {
return new EmptyResponse(403);
}
// list stale feeds which should be checked for updates // list stale feeds which should be checked for updates
$feeds = Arsse::$db->feedListStale(); $feeds = Arsse::$db->feedListStale();
$out = []; $out = [];
@ -372,6 +375,9 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// refresh a feed // refresh a feed
protected function feedUpdate(array $url, array $data): ResponseInterface { protected function feedUpdate(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) {
return new EmptyResponse(403);
}
try { try {
Arsse::$db->feedUpdate($data['feedId']); Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) { } catch (ExceptionInput $e) {
@ -667,11 +673,17 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
protected function cleanupBefore(array $url, array $data): ResponseInterface { protected function cleanupBefore(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) {
return new EmptyResponse(403);
}
Service::cleanupPre(); Service::cleanupPre();
return new EmptyResponse(204); return new EmptyResponse(204);
} }
protected function cleanupAfter(array $url, array $data): ResponseInterface { protected function cleanupAfter(array $url, array $data): ResponseInterface {
if (!$this->isAdmin()) {
return new EmptyResponse(403);
}
Service::cleanupPost(); Service::cleanupPost();
return new EmptyResponse(204); return new EmptyResponse(204);
} }

View file

@ -317,6 +317,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
// create a mock user manager // create a mock user manager
Arsse::$user = \Phake::mock(User::class); Arsse::$user = \Phake::mock(User::class);
Arsse::$user->id = "john.doe@example.com"; Arsse::$user->id = "john.doe@example.com";
\Phake::when(Arsse::$user)->propertiesGet->thenReturn(['admin' => true]);
// create a mock database interface // create a mock database interface
Arsse::$db = \Phake::mock(Database::class); Arsse::$db = \Phake::mock(Database::class);
$this->transaction = \Phake::mock(Transaction::class); $this->transaction = \Phake::mock(Transaction::class);
@ -629,6 +630,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertMessage($exp, $this->req("GET", "/feeds/all")); $this->assertMessage($exp, $this->req("GET", "/feeds/all"));
} }
public function testListStaleFeedsWithoutAuthority(): void {
\Phake::when(Arsse::$user)->propertiesGet->thenReturn(['admin' => false]);
$exp = new EmptyResponse(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/all"));
\Phake::verify(Arsse::$db, \Phake::times(0))->feedListStale;
}
public function testUpdateAFeed(): void { public function testUpdateAFeed(): void {
$in = [ $in = [
['feedId' => 42], // valid ['feedId' => 42], // valid
@ -650,6 +658,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4]))); $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4])));
} }
public function testUpdateAFeedWithoutAuthority(): void {
\Phake::when(Arsse::$user)->propertiesGet->thenReturn(['admin' => false]);
$exp = new EmptyResponse(403);
$this->assertMessage($exp, $this->req("GET", "/feeds/update", ['feedId' => 42]));
\Phake::verify(Arsse::$db, \Phake::times(0))->feedUpdate;
}
/** @dataProvider provideArticleQueries */ /** @dataProvider provideArticleQueries */
public function testListArticles(string $url, array $in, Context $c, $out, ResponseInterface $exp): void { public function testListArticles(string $url, array $in, Context $c, $out, ResponseInterface $exp): void {
if ($out instanceof \Exception) { if ($out instanceof \Exception) {
@ -849,6 +864,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
\Phake::verify(Arsse::$db)->feedCleanup(); \Phake::verify(Arsse::$db)->feedCleanup();
} }
public function testCleanUpBeforeUpdateWithoutAuthority(): void {
\Phake::when(Arsse::$user)->propertiesGet->thenReturn(['admin' => false]);
$exp = new EmptyResponse(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
\Phake::verify(Arsse::$db, \Phake::times(0))->feedCleanup;
}
public function testCleanUpAfterUpdate(): void { public function testCleanUpAfterUpdate(): void {
\Phake::when(Arsse::$db)->articleCleanup()->thenReturn(true); \Phake::when(Arsse::$db)->articleCleanup()->thenReturn(true);
$exp = new EmptyResponse(204); $exp = new EmptyResponse(204);
@ -856,6 +878,13 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
\Phake::verify(Arsse::$db)->articleCleanup(); \Phake::verify(Arsse::$db)->articleCleanup();
} }
public function testCleanUpAfterUpdateWithoutAuthority(): void {
\Phake::when(Arsse::$user)->propertiesGet->thenReturn(['admin' => false]);
$exp = new EmptyResponse(403);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
\Phake::verify(Arsse::$db, \Phake::times(0))->feedCleanup;
}
public function testQueryTheUserStatus(): void { public function testQueryTheUserStatus(): void {
$act = $this->req("GET", "/user"); $act = $this->req("GET", "/user");
$exp = new Response([ $exp = new Response([