mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Add access tokens to the db, with relevant code
Tokens are similar to sessions in that they stand in for users, but the protocol handlers will manage them; Fever login hashes are the originating use case for them. These must never expire, for example, and we need to specify their values. This commit also performs a bit of database clean-up
This commit is contained in:
parent
5de1844f6d
commit
38bdde1167
17 changed files with 333 additions and 40 deletions
|
@ -27,6 +27,7 @@ use JKingWeb\Arsse\Misc\ValueInfo;
|
||||||
* - Editions, identifying authorial modifications to articles
|
* - Editions, identifying authorial modifications to articles
|
||||||
* - Labels, which belong to users and can be assigned to multiple articles
|
* - Labels, which belong to users and can be assigned to multiple articles
|
||||||
* - Sessions, used by some protocols to identify users across periods of time
|
* - Sessions, used by some protocols to identify users across periods of time
|
||||||
|
* - Tokens, similar to sessions, but with more control over their properties
|
||||||
* - Metadata, used internally by the server
|
* - Metadata, used internally by the server
|
||||||
*
|
*
|
||||||
* The various methods of this class perform operations on these things, with
|
* The various methods of this class perform operations on these things, with
|
||||||
|
@ -380,6 +381,59 @@ class Database {
|
||||||
return (($now + $diff) >= $expiry->getTimestamp());
|
return (($now + $diff) >= $expiry->getTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates a new token for the given user in the given class
|
||||||
|
*
|
||||||
|
* @param string $user The user for whom to create the token
|
||||||
|
* @param string $class The class of the token e.g. the protocol name
|
||||||
|
* @param string|null $id The value of the token; if none is provided a UUID will be generated
|
||||||
|
* @param \DateTimeInterface|null $expires An optional expiry date and time for the token
|
||||||
|
*/
|
||||||
|
public function tokenCreate(string $user, string $class, string $id = null, \DateTimeInterface $expires = null): string {
|
||||||
|
// If the user isn't authorized to perform this action then throw an exception.
|
||||||
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
|
}
|
||||||
|
// generate a token if it's not provided
|
||||||
|
$id = $id ?? UUID::mint()->hex;
|
||||||
|
// save the token to the database
|
||||||
|
$this->db->prepare("INSERT INTO arsse_tokens(id,class,\"user\",expires) values(?,?,?,?)", "str", "str", "str", "datetime")->run($id, $class, $user, $expires);
|
||||||
|
// return the ID
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Revokes one or all tokens for a user in a class
|
||||||
|
*
|
||||||
|
* @param string $user The user who owns the token to be revoked
|
||||||
|
* @param string $class The class of the token e.g. the protocol name
|
||||||
|
* @param string|null $id The ID of a specific token, or null for all tokens in the class
|
||||||
|
*/
|
||||||
|
public function tokenRevoke(string $user, string $class, string $id = null): bool {
|
||||||
|
// If the user isn't authorized to perform this action then throw an exception.
|
||||||
|
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||||
|
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
|
}
|
||||||
|
if (is_null($id)) {
|
||||||
|
$out = $this->db->prepare("DELETE FROM arsse_tokens where \"user\" = ? and class = ?", "str", "str")->run($user, $class)->changes();
|
||||||
|
} else {
|
||||||
|
$out = $this->db->prepare("DELETE FROM arsse_tokens where \"user\" = ? and class = ? and id = ?", "str", "str", "str")->run($user, $class, $id)->changes();
|
||||||
|
}
|
||||||
|
return (bool) $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Look up data associated with a token */
|
||||||
|
public function tokenLookup(string $class, string $id): array {
|
||||||
|
$out = $this->db->prepare("SELECT id,class,\"user\",created,expires from arsse_tokens where class = ? and id = ? and expires > CURRENT_TIMESTAMP", "str", "str")->run($class, $id)->getRow();
|
||||||
|
if (!$out) {
|
||||||
|
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "token", 'id' => $id]);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes expires tokens from the database, returning the number of deleted tokens */
|
||||||
|
public function tokenCleanup(): int {
|
||||||
|
return $this->db->query("DELETE FROM arsse_tokens where expires < CURRENT_TIMESTAMP")->changes();
|
||||||
|
}
|
||||||
|
|
||||||
/** Adds a folder for containing newsfeed subscriptions, returning an integer identifying the created folder
|
/** Adds a folder for containing newsfeed subscriptions, returning an integer identifying the created folder
|
||||||
*
|
*
|
||||||
* The $data array may contain the following keys:
|
* The $data array may contain the following keys:
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
$this->exec($q);
|
$this->exec($q);
|
||||||
}
|
}
|
||||||
// get the maximum packet size; parameter strings larger than this size need to be chunked
|
// get the maximum packet size; parameter strings larger than this size need to be chunked
|
||||||
$this->packetSize = (int) $this->query("select variable_value from performance_schema.session_variables where variable_name = 'max_allowed_packet'")->getValue();
|
$this->packetSize = (int) $this->query("SELECT variable_value from performance_schema.session_variables where variable_name = 'max_allowed_packet'")->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function makeSetupQueries(): array {
|
public static function makeSetupQueries(): array {
|
||||||
|
|
|
@ -20,4 +20,22 @@ create table arsse_tag_members(
|
||||||
primary key(tag,subscription)
|
primary key(tag,subscription)
|
||||||
) character set utf8mb4 collate utf8mb4_unicode_ci;
|
) character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
create table arsse_tokens(
|
||||||
|
id varchar(255) not null,
|
||||||
|
class varchar(255) not null,
|
||||||
|
"user" varchar(255) not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
created datetime(0) not null default CURRENT_TIMESTAMP,
|
||||||
|
expires datetime(0),
|
||||||
|
primary key(id,class)
|
||||||
|
) character set utf8mb4 collate utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
alter table arsse_users drop column name;
|
||||||
|
alter table arsse_users drop column avatar_type;
|
||||||
|
alter table arsse_users drop column avatar_data;
|
||||||
|
alter table arsse_users drop column admin;
|
||||||
|
alter table arsse_users drop column rights;
|
||||||
|
|
||||||
|
drop table arsse_users_meta;
|
||||||
|
|
||||||
|
|
||||||
update arsse_meta set value = '5' where "key" = 'schema_version';
|
update arsse_meta set value = '5' where "key" = 'schema_version';
|
||||||
|
|
|
@ -20,4 +20,21 @@ create table arsse_tag_members(
|
||||||
primary key(tag,subscription)
|
primary key(tag,subscription)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table arsse_tokens(
|
||||||
|
id text,
|
||||||
|
class text not null,
|
||||||
|
"user" text not null references arsse_users(id) on delete cascade on update cascade,
|
||||||
|
created timestamp(0) without time zone not null default CURRENT_TIMESTAMP,
|
||||||
|
expires timestamp(0) without time zone,
|
||||||
|
primary key(id,class)
|
||||||
|
);
|
||||||
|
|
||||||
|
alter table arsse_users drop column name;
|
||||||
|
alter table arsse_users drop column avatar_type;
|
||||||
|
alter table arsse_users drop column avatar_data;
|
||||||
|
alter table arsse_users drop column admin;
|
||||||
|
alter table arsse_users drop column rights;
|
||||||
|
|
||||||
|
drop table arsse_users_meta;
|
||||||
|
|
||||||
update arsse_meta set value = '5' where "key" = 'schema_version';
|
update arsse_meta set value = '5' where "key" = 'schema_version';
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
create table arsse_sessions(
|
create table arsse_sessions(
|
||||||
-- sessions for Tiny Tiny RSS (and possibly others)
|
-- sessions for Tiny Tiny RSS (and possibly others)
|
||||||
id text primary key, -- UUID of session
|
id text primary key, -- UUID of session
|
||||||
created text not null default CURRENT_TIMESTAMP, -- Session start timestamp
|
created text not null default CURRENT_TIMESTAMP, -- session start timestamp
|
||||||
expires text not null, -- Time at which session is no longer valid
|
expires text not null, -- time at which session is no longer valid
|
||||||
user text not null references arsse_users(id) on delete cascade on update cascade -- user associated with the session
|
user text not null references arsse_users(id) on delete cascade on update cascade -- user associated with the session
|
||||||
) without rowid;
|
) without rowid;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,59 @@ create table arsse_tag_members(
|
||||||
primary key(tag,subscription) -- only one association of a given tag to a given subscription
|
primary key(tag,subscription) -- only one association of a given tag to a given subscription
|
||||||
) without rowid;
|
) without rowid;
|
||||||
|
|
||||||
|
create table arsse_tokens(
|
||||||
|
-- access tokens that are managed by the protocol handler and may optionally expire
|
||||||
|
id text, -- token identifier
|
||||||
|
class text not null, -- symbolic name of the protocol handler managing the token
|
||||||
|
user text not null references arsse_users(id) on delete cascade on update cascade, -- user associated with the token
|
||||||
|
created text not null default CURRENT_TIMESTAMP, -- creation timestamp
|
||||||
|
expires text, -- time at which token is no longer valid
|
||||||
|
primary key(id,class) -- tokens must be unique for their class
|
||||||
|
) without rowid;
|
||||||
|
|
||||||
|
|
||||||
|
-- clean up the user tables to remove unused stuff
|
||||||
|
-- if any of the removed things are implemented in future, necessary structures will be added back in at that time
|
||||||
|
|
||||||
|
create table arsse_users_new(
|
||||||
|
-- users
|
||||||
|
id text primary key not null collate nocase, -- user id
|
||||||
|
password text -- password, salted and hashed; if using external authentication this would be blank
|
||||||
|
) without rowid;
|
||||||
|
insert into arsse_users_new select id,password from arsse_users;
|
||||||
|
drop table arsse_users;
|
||||||
|
alter table arsse_users_new rename to arsse_users;
|
||||||
|
|
||||||
|
drop table arsse_users_meta;
|
||||||
|
|
||||||
|
|
||||||
|
-- use WITHOUT ROWID tables when possible; this is an SQLite-specific change
|
||||||
|
|
||||||
|
create table arsse_meta_new(
|
||||||
|
-- application metadata
|
||||||
|
key text primary key not null, -- metadata key
|
||||||
|
value text -- metadata value, serialized as a string
|
||||||
|
) without rowid;
|
||||||
|
insert into arsse_meta_new select * from arsse_meta;
|
||||||
|
drop table arsse_meta;
|
||||||
|
alter table arsse_meta_new rename to arsse_meta;
|
||||||
|
|
||||||
|
create table arsse_marks_new(
|
||||||
|
-- users' actions on newsfeed entries
|
||||||
|
article integer not null references arsse_articles(id) on delete cascade, -- article associated with the marks
|
||||||
|
subscription integer not null references arsse_subscriptions(id) on delete cascade on update cascade, -- subscription associated with the marks; the subscription in turn belongs to a user
|
||||||
|
read boolean not null default 0, -- whether the article has been read
|
||||||
|
starred boolean not null default 0, -- whether the article is starred
|
||||||
|
modified text, -- time at which an article was last modified by a given user
|
||||||
|
note text not null default '', -- Tiny Tiny RSS freeform user note
|
||||||
|
touched boolean not null default 0, -- used to indicate a record has been modified during the course of some transactions
|
||||||
|
primary key(article,subscription) -- no more than one mark-set per article per user
|
||||||
|
) without rowid;
|
||||||
|
insert into arsse_marks_new select * from arsse_marks;
|
||||||
|
drop table arsse_marks;
|
||||||
|
alter table arsse_marks_new rename to arsse_marks;
|
||||||
|
|
||||||
|
|
||||||
-- set version marker
|
-- set version marker
|
||||||
pragma user_version = 5;
|
pragma user_version = 5;
|
||||||
update arsse_meta set value = '5' where "key" = 'schema_version';
|
update arsse_meta set value = '5' where "key" = 'schema_version';
|
||||||
|
|
|
@ -20,6 +20,7 @@ abstract class Base extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
use SeriesMeta;
|
use SeriesMeta;
|
||||||
use SeriesUser;
|
use SeriesUser;
|
||||||
use SeriesSession;
|
use SeriesSession;
|
||||||
|
use SeriesToken;
|
||||||
use SeriesFolder;
|
use SeriesFolder;
|
||||||
use SeriesFeed;
|
use SeriesFeed;
|
||||||
use SeriesSubscription;
|
use SeriesSubscription;
|
||||||
|
|
|
@ -19,13 +19,12 @@ trait SeriesArticle {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
["john.doe@example.org", "", "John Doe"],
|
["john.doe@example.org", ""],
|
||||||
["john.doe@example.net", "", "John Doe"],
|
["john.doe@example.net", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
|
|
|
@ -29,11 +29,10 @@ trait SeriesCleanup {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_sessions' => [
|
'arsse_sessions' => [
|
||||||
|
@ -51,6 +50,20 @@ trait SeriesCleanup {
|
||||||
["e", $daysago, $nowish, "jane.doe@example.com"], // created more than a day ago and expired, thus deleted
|
["e", $daysago, $nowish, "jane.doe@example.com"], // created more than a day ago and expired, thus deleted
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'arsse_tokens' => [
|
||||||
|
'columns' => [
|
||||||
|
'id' => "str",
|
||||||
|
'class' => "str",
|
||||||
|
'user' => "str",
|
||||||
|
'expires' => "datetime",
|
||||||
|
],
|
||||||
|
'rows' => [
|
||||||
|
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
|
||||||
|
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $weeksago], // expired
|
||||||
|
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null],
|
||||||
|
["da772f8fa13c11e78667001e673b2560", "class.class", "john.doe@example.com", $soon],
|
||||||
|
],
|
||||||
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => "int",
|
'id' => "int",
|
||||||
|
@ -226,4 +239,15 @@ trait SeriesCleanup {
|
||||||
}
|
}
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCleanUpExpiredTokens() {
|
||||||
|
Arsse::$db->tokenCleanup();
|
||||||
|
$state = $this->primeExpectations($this->data, [
|
||||||
|
'arsse_tokens' => ["id", "class"]
|
||||||
|
]);
|
||||||
|
foreach ([2] as $id) {
|
||||||
|
unset($state['arsse_tokens']['rows'][$id - 1]);
|
||||||
|
}
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,10 @@ trait SeriesFeed {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
|
|
|
@ -16,11 +16,10 @@ trait SeriesFolder {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_folders' => [
|
'arsse_folders' => [
|
||||||
|
|
|
@ -18,13 +18,12 @@ trait SeriesLabel {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
["john.doe@example.org", "", "John Doe"],
|
["john.doe@example.org", ""],
|
||||||
["john.doe@example.net", "", "John Doe"],
|
["john.doe@example.net", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_folders' => [
|
'arsse_folders' => [
|
||||||
|
|
|
@ -27,11 +27,10 @@ trait SeriesSession {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_sessions' => [
|
'arsse_sessions' => [
|
||||||
|
|
|
@ -18,11 +18,10 @@ trait SeriesSubscription {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_folders' => [
|
'arsse_folders' => [
|
||||||
|
|
|
@ -17,13 +17,12 @@ trait SeriesTag {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["jane.doe@example.com", "", "Jane Doe"],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe"],
|
["john.doe@example.com", ""],
|
||||||
["john.doe@example.org", "", "John Doe"],
|
["john.doe@example.org", ""],
|
||||||
["john.doe@example.net", "", "John Doe"],
|
["john.doe@example.net", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'arsse_feeds' => [
|
'arsse_feeds' => [
|
||||||
|
|
135
tests/cases/Database/SeriesToken.php
Normal file
135
tests/cases/Database/SeriesToken.php
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
/** @license MIT
|
||||||
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
|
* See LICENSE and AUTHORS files for details */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\TestCase\Database;
|
||||||
|
|
||||||
|
use JKingWeb\Arsse\Arsse;
|
||||||
|
use JKingWeb\Arsse\Misc\Date;
|
||||||
|
use Phake;
|
||||||
|
|
||||||
|
trait SeriesToken {
|
||||||
|
protected function setUpSeriesToken() {
|
||||||
|
// set up the test data
|
||||||
|
$past = gmdate("Y-m-d H:i:s", strtotime("now - 1 minute"));
|
||||||
|
$future = gmdate("Y-m-d H:i:s", strtotime("now + 1 minute"));
|
||||||
|
$faroff = gmdate("Y-m-d H:i:s", strtotime("now + 1 hour"));
|
||||||
|
$old = gmdate("Y-m-d H:i:s", strtotime("now - 2 days"));
|
||||||
|
$this->data = [
|
||||||
|
'arsse_users' => [
|
||||||
|
'columns' => [
|
||||||
|
'id' => 'str',
|
||||||
|
'password' => 'str',
|
||||||
|
],
|
||||||
|
'rows' => [
|
||||||
|
["jane.doe@example.com", ""],
|
||||||
|
["john.doe@example.com", ""],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'arsse_tokens' => [
|
||||||
|
'columns' => [
|
||||||
|
'id' => "str",
|
||||||
|
'class' => "str",
|
||||||
|
'user' => "str",
|
||||||
|
'expires' => "datetime",
|
||||||
|
],
|
||||||
|
'rows' => [
|
||||||
|
["80fa94c1a11f11e78667001e673b2560", "fever.login", "jane.doe@example.com", $faroff],
|
||||||
|
["27c6de8da13311e78667001e673b2560", "fever.login", "jane.doe@example.com", $past], // expired
|
||||||
|
["ab3b3eb8a13311e78667001e673b2560", "class.class", "jane.doe@example.com", null],
|
||||||
|
["da772f8fa13c11e78667001e673b2560", "class.class", "john.doe@example.com", $future],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDownSeriesToken() {
|
||||||
|
unset($this->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookUpAValidToken() {
|
||||||
|
$exp1 = [
|
||||||
|
'id' => "80fa94c1a11f11e78667001e673b2560",
|
||||||
|
'class' => "fever.login",
|
||||||
|
'user' => "jane.doe@example.com"
|
||||||
|
];
|
||||||
|
$exp2 = [
|
||||||
|
'id' => "da772f8fa13c11e78667001e673b2560",
|
||||||
|
'class' => "class.class",
|
||||||
|
'user' => "john.doe@example.com"
|
||||||
|
];
|
||||||
|
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
||||||
|
$this->assertArraySubset($exp2, Arsse::$db->tokenLookup("class.class", "da772f8fa13c11e78667001e673b2560"));
|
||||||
|
// token lookup should not check authorization
|
||||||
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
|
$this->assertArraySubset($exp1, Arsse::$db->tokenLookup("fever.login", "80fa94c1a11f11e78667001e673b2560"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookUpAMissingToken() {
|
||||||
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
|
Arsse::$db->tokenLookup("class", "thisTokenDoesNotExist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookUpAnExpiredToken() {
|
||||||
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
|
Arsse::$db->tokenLookup("fever.login", "27c6de8da13311e78667001e673b2560");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLookUpATokenOfTheWrongClass() {
|
||||||
|
$this->assertException("subjectMissing", "Db", "ExceptionInput");
|
||||||
|
Arsse::$db->tokenLookup("some.class", "80fa94c1a11f11e78667001e673b2560");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateAToken() {
|
||||||
|
$user = "jane.doe@example.com";
|
||||||
|
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "class", "expires", "user"]]);
|
||||||
|
$id = Arsse::$db->tokenCreate($user, "fever.login");
|
||||||
|
$state['arsse_tokens']['rows'][] = [$id, "fever.login", null, $user];
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
$id = Arsse::$db->tokenCreate($user, "fever.login", null, new \DateTime("2020-01-01T00:00:00Z"));
|
||||||
|
$state['arsse_tokens']['rows'][] = [$id, "fever.login", "2020-01-01 00:00:00", $user];
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
Arsse::$db->tokenCreate($user, "fever.login", "token!", new \DateTime("2021-01-01T00:00:00Z"));
|
||||||
|
$state['arsse_tokens']['rows'][] = ["token!", "fever.login", "2021-01-01 00:00:00", $user];
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateATokenWithoutAuthority() {
|
||||||
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
|
Arsse::$db->tokenCreate("fever.login", "jane.doe@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRevokeAToken() {
|
||||||
|
$user = "jane.doe@example.com";
|
||||||
|
$id = "80fa94c1a11f11e78667001e673b2560";
|
||||||
|
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
||||||
|
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "expires", "user"]]);
|
||||||
|
unset($state['arsse_tokens']['rows'][0]);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
// revoking a token which does not exist is not an error
|
||||||
|
$this->assertFalse(Arsse::$db->tokenRevoke($user, "fever.login", $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRevokeAllTokens() {
|
||||||
|
$user = "jane.doe@example.com";
|
||||||
|
$state = $this->primeExpectations($this->data, ['arsse_tokens' => ["id", "expires", "user"]]);
|
||||||
|
$this->assertTrue(Arsse::$db->tokenRevoke($user, "fever.login"));
|
||||||
|
unset($state['arsse_tokens']['rows'][0]);
|
||||||
|
unset($state['arsse_tokens']['rows'][1]);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
$this->assertTrue(Arsse::$db->tokenRevoke($user, "class.class"));
|
||||||
|
unset($state['arsse_tokens']['rows'][2]);
|
||||||
|
$this->compareExpectations($state);
|
||||||
|
// revoking tokens which do not exist is not an error
|
||||||
|
$this->assertFalse(Arsse::$db->tokenRevoke($user, "unknown.class"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRevokeATokenWithoutAuthority() {
|
||||||
|
Phake::when(Arsse::$user)->authorize->thenReturn(false);
|
||||||
|
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
|
||||||
|
Arsse::$db->tokenRevoke("jane.doe@example.com", "fever.login");
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,13 +17,11 @@ trait SeriesUser {
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'id' => 'str',
|
'id' => 'str',
|
||||||
'password' => 'str',
|
'password' => 'str',
|
||||||
'name' => 'str',
|
|
||||||
'rights' => 'int',
|
|
||||||
],
|
],
|
||||||
'rows' => [
|
'rows' => [
|
||||||
["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', "Hard Lip Herbert", 100], // password is hash of "secret"
|
["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW'], // password is hash of "secret"
|
||||||
["jane.doe@example.com", "", "Jane Doe", 0],
|
["jane.doe@example.com", ""],
|
||||||
["john.doe@example.com", "", "John Doe", 0],
|
["john.doe@example.com", ""],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -68,8 +66,8 @@ trait SeriesUser {
|
||||||
public function testAddANewUser() {
|
public function testAddANewUser() {
|
||||||
$this->assertTrue(Arsse::$db->userAdd("john.doe@example.org", ""));
|
$this->assertTrue(Arsse::$db->userAdd("john.doe@example.org", ""));
|
||||||
Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd");
|
Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd");
|
||||||
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id','name','rights']]);
|
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id']]);
|
||||||
$state['arsse_users']['rows'][] = ["john.doe@example.org", null, 0];
|
$state['arsse_users']['rows'][] = ["john.doe@example.org"];
|
||||||
$this->compareExpectations($state);
|
$this->compareExpectations($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue