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

Preliminary implementation of TTRSS sessions (needs tests; may not work)

This commit is contained in:
J. King 2017-09-16 19:57:33 -04:00
parent c393dfc42b
commit 474d32e54f
9 changed files with 327 additions and 23 deletions

View file

@ -24,7 +24,9 @@
"ext-hash": "*", "ext-hash": "*",
"fguillot/picofeed": ">=0.1.31", "fguillot/picofeed": ">=0.1.31",
"hosteurope/password-generator": "^1.0", "hosteurope/password-generator": "^1.0",
"docopt/docopt": "^1.0" "docopt/docopt": "^1.0",
"jkingweb/druuid": "^3.0",
"phpseclib/phpseclib": "^2.0"
}, },
"require-dev": { "require-dev": {
"mikey179/vfsStream": "^1.6", "mikey179/vfsStream": "^1.6",

240
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "125797db6f29f530c2f89209cc4f462d", "content-hash": "d00fd63e825db5ce16878c1639f362f3",
"packages": [ "packages": [
{ {
"name": "docopt/docopt", "name": "docopt/docopt",
@ -145,6 +145,143 @@
"description": "Password generator for generating policy-compliant passwords.", "description": "Password generator for generating policy-compliant passwords.",
"time": "2016-12-08T09:32:12+00:00" "time": "2016-12-08T09:32:12+00:00"
}, },
{
"name": "jkingweb/druuid",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/JKingweb/DrUUID.git",
"reference": "ca88019069f03ee9c0b1bb6b0200f421bbc9607e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JKingweb/DrUUID/zipball/ca88019069f03ee9c0b1bb6b0200f421bbc9607e",
"reference": "ca88019069f03ee9c0b1bb6b0200f421bbc9607e",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"suggest": {
"ext-bcmath": "Supported alternative to GMP on 32-bit systems",
"ext-gmp": "Recommended on 32-bit installations for time-base UUIDs",
"phpseclib/phpseclib": "Supported alternative to GMP or BC Math on 32-bit systems (either v1.x or v2.x)"
},
"type": "library",
"autoload": {
"psr-4": {
"JKingWeb\\DrUUID\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "J. King",
"email": "jking@jkingweb.ca",
"homepage": "https://jkingweb.ca/"
}
],
"description": "DrUUID RFC 4122 library for PHP",
"keywords": [
"uuid"
],
"time": "2017-02-09T14:17:01+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/34a7699e6f31b1ef4035ee36444407cecf9f56aa",
"reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phing/phing": "~2.7",
"phpunit/phpunit": "~4.0",
"sami/sami": "~2.0",
"squizlabs/php_codesniffer": "~2.0"
},
"suggest": {
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"time": "2017-06-05T06:31:10+00:00"
},
{ {
"name": "zendframework/zendxml", "name": "zendframework/zendxml",
"version": "1.0.2", "version": "1.0.2",
@ -310,6 +447,68 @@
], ],
"time": "2012-12-19T10:50:58+00:00" "time": "2012-12-19T10:50:58+00:00"
}, },
{
"name": "composer/semver",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
"shasum": ""
},
"require": {
"php": "^5.3.2 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.5 || ^5.0.5",
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Semver\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Adermann",
"email": "naderman@naderman.de",
"homepage": "http://www.naderman.de"
},
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
},
{
"name": "Rob Bast",
"email": "rob.bast@gmail.com",
"homepage": "http://robbast.nl"
}
],
"description": "Semver library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
"validation",
"versioning"
],
"time": "2016-08-30T16:08:34+00:00"
},
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
"version": "1.2.0", "version": "1.2.0",
@ -561,19 +760,20 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v2.2.6", "version": "v2.2.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4" "reference": "b6202ccad4c00778887e7e8282d52f854802b59a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c1cc52c242f17c4d52d9601159631da488fac7a4", "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b6202ccad4c00778887e7e8282d52f854802b59a",
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4", "reference": "b6202ccad4c00778887e7e8282d52f854802b59a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/semver": "^1.4",
"doctrine/annotations": "^1.2", "doctrine/annotations": "^1.2",
"ext-json": "*", "ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
@ -641,7 +841,7 @@
} }
], ],
"description": "A tool to automatically fix PHP code style", "description": "A tool to automatically fix PHP code style",
"time": "2017-08-22T14:08:16+00:00" "time": "2017-09-11T14:27:07+00:00"
}, },
{ {
"name": "gecko-packages/gecko-php-unit", "name": "gecko-packages/gecko-php-unit",
@ -1599,16 +1799,16 @@
}, },
{ {
"name": "phake/phake", "name": "phake/phake",
"version": "v3.0.0", "version": "v3.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/mlively/Phake.git", "url": "https://github.com/mlively/Phake.git",
"reference": "c242d6a8376bd3280d903d95725d3e1e2f9efadc" "reference": "949340efc3cd99b401a0dd1a5ffeac690a3c3967"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/mlively/Phake/zipball/c242d6a8376bd3280d903d95725d3e1e2f9efadc", "url": "https://api.github.com/repos/mlively/Phake/zipball/949340efc3cd99b401a0dd1a5ffeac690a3c3967",
"reference": "c242d6a8376bd3280d903d95725d3e1e2f9efadc", "reference": "949340efc3cd99b401a0dd1a5ffeac690a3c3967",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1653,7 +1853,7 @@
"mock", "mock",
"testing" "testing"
], ],
"time": "2017-07-04T20:09:48+00:00" "time": "2017-09-06T12:09:44+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -2226,22 +2426,22 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "v1.7.0", "version": "v1.7.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073" "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073", "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0", "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0", "sebastian/comparator": "^1.1|^2.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0" "sebastian/recursion-context": "^1.0|^2.0|^3.0"
}, },
@ -2252,7 +2452,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.6.x-dev" "dev-master": "1.7.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -2285,7 +2485,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2017-03-02T20:05:34+00:00" "time": "2017-09-04T11:05:03+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -3764,7 +3964,7 @@
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v3.3.8", "version": "v3.3.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/options-resolver.git", "url": "https://github.com/symfony/options-resolver.git",
@ -4340,7 +4540,7 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.3.8", "version": "v3.3.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",

View file

@ -56,6 +56,7 @@ abstract class AbstractException extends \Exception {
"User/Exception.authMissing" => 10411, "User/Exception.authMissing" => 10411,
"User/Exception.authFailed" => 10412, "User/Exception.authFailed" => 10412,
"User/ExceptionAuthz.notAuthorized" => 10421, "User/ExceptionAuthz.notAuthorized" => 10421,
"User/ExceptionSession.invalid" => 10431,
"Feed/Exception.invalidCertificate" => 10501, "Feed/Exception.invalidCertificate" => 10501,
"Feed/Exception.invalidUrl" => 10502, "Feed/Exception.invalidUrl" => 10502,
"Feed/Exception.maxRedirect" => 10503, "Feed/Exception.maxRedirect" => 10503,

View file

@ -28,10 +28,16 @@ class Conf {
public $userPreAuth = false; public $userPreAuth = false;
/** @var integer Desired length of temporary user passwords */ /** @var integer Desired length of temporary user passwords */
public $userTempPasswordLength = 20; public $userTempPasswordLength = 20;
/** @var string Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 1 hour)
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $userSessionTimeout = "PT1H";
/** @var string Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 24 hours);
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $userSessionLifetime = "PT24H";
/** @var string Class of the background feed update service driver in use (Forking by default) */ /** @var string Class of the background feed update service driver in use (Forking by default) */
public $serviceDriver = Service\Forking\Driver::class; public $serviceDriver = Service\Forking\Driver::class;
/** @var string The interval between checks for new feeds, as an ISO 8601 duration /** @var string The interval between checks for new articles, as an ISO 8601 duration
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */ * @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $serviceFrequency = "PT2M"; public $serviceFrequency = "PT2M";
/** @var integer Number of concurrent feed updates to perform */ /** @var integer Number of concurrent feed updates to perform */

View file

@ -3,6 +3,7 @@ declare(strict_types=1);
namespace JKingWeb\Arsse; namespace JKingWeb\Arsse;
use PasswordGenerator\Generator as PassGen; use PasswordGenerator\Generator as PassGen;
use JKingWeb\DrUUID\UUID;
use JKingWeb\Arsse\Misc\Query; use JKingWeb\Arsse\Misc\Query;
use JKingWeb\Arsse\Misc\Context; use JKingWeb\Arsse\Misc\Context;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
@ -223,6 +224,61 @@ class Database {
return true; return true;
} }
public function sessionCreate(string $user): 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 new session ID and expiry date
$id = UUID::mint()->hex;
$expires = Date::add(Arsse::$conf->userSessionTimeout);
// save the session to the database
$this->db->prepare("INSERT INTO arsse_sessions(id,expires,user) values(?,?,?)", "str", "datetime", "str")->run($id, $expires, $user);
// return the ID
return $id;
}
public function sessionDestroy(string $user, string $id): 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]);
}
// delete the session and report success.
return (bool) $this->db->prepare("DELETE FROM arsse_sessions where id is ? and user is ?", "str", "str")->run($id, $user)->changes();
}
public function sessionResume(string $id): array {
$maxage = Date::sub(Arsse::$conf->userSessionLifetime);
$out = $this->db->prepare("SELECT * from arsse_sessions where id is ? and expires > CURRENT_TIMESTAMP and created > ?", "str", "datetime")->run($id, $maxage)->getRow();
// if the session does not exist or is expired, throw an exception
if (!$out) {
throw new User\ExceptionSession("invalid", $id);
}
// otherwise populate the session user when appropriate
if (Arsse::$user) {
Arsse::$user->id = $out['user'];
}
// if we're more than half-way from the session expiring, renew it
if ($this->sessionExpiringSoon(Date::normalize($out['expires'], "sql"))) {
$expires = Date::add(Arsse::$conf->userSessionTimeout);
$this->db->prepare("UPDATE arsse_sessions set expires = ? where id is ?", "datetime", "str")->run($expires, $id);
}
return $out;
}
public function sessionCleanup(): int {
return $this->db->query("DELETE FROM arsse_sessions where expires < CURRENT_TIMESTAMP")->changes();
}
protected function sessionExpiringSoon(DateTimeInterface $expiry): bool {
// calculate half the session timeout as a number of seconds
$now = time();
$max = Date::add(Arsse::$conf->userSessionTimeout, $now)->getTimestamp();
$diff = intdiv($max - $now, 2);
// determine if the expiry time is less than half the session timeout into the future
return (($now + $diff) >= $expiry->getTimestamp());
}
public function folderAdd(string $user, array $data): int { public function folderAdd(string $user, array $data): int {
// If the user isn't authorized to perform this action then throw an exception. // If the user isn't authorized to perform this action then throw an exception.
if (!Arsse::$user->authorize($user, __FUNCTION__)) { if (!Arsse::$user->authorize($user, __FUNCTION__)) {

View file

@ -84,7 +84,10 @@ class Service {
public static function cleanupPre(): bool { public static function cleanupPre(): bool {
// mark unsubscribed feeds as orphaned and delete orphaned feeds that are beyond their retention period // mark unsubscribed feeds as orphaned and delete orphaned feeds that are beyond their retention period
return Arsse::$db->feedCleanup(); Arsse::$db->feedCleanup();
// delete expired log-in sessions
Arsse::$db->sessionCleanup();
return true;
} }
public static function cleanupPost(): bool { public static function cleanupPost(): bool {

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
namespace JKingWeb\Arsse\User;
class ExceptionSession extends Exception {
}

View file

@ -137,6 +137,7 @@ return [
}} }}
other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}} other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}}
}', }',
'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist',
'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate', 'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
'Exception.JKingWeb/Arsse/Feed/Exception.invalidUrl' => 'Feed URL "{url}" is invalid', 'Exception.JKingWeb/Arsse/Feed/Exception.invalidUrl' => 'Feed URL "{url}" is invalid',
'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections', 'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',

29
sql/SQLite3/1.sql Normal file
View file

@ -0,0 +1,29 @@
-- Sessions for Tiny Tiny RSS (and possibly others)
create table arsse_sessions (
id text primary key, -- UUID of session
created datetime not null default CURRENT_TIMESTAMP, -- Session start timestamp
expires datetime 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
) without rowid;
-- User-defined article labels for Tiny Tiny RSS
create table arsse_labels (
id integer primary key, -- numeric ID
owner text not null references arsse_users(id) on delete cascade on update cascade, -- owning user
name text not null, -- label text
foreground text, -- foreground (text) colour in hexdecimal RGB
background text, -- background colour in hexadecimal RGB
unique(owner,name)
);
-- Labels assignments for articles
create table arsse_label_members (
label integer not null references arsse_labels(id) on delete cascade,
article integer not null references arsse_articles(id) on delete cascade,
subscription integer not null references arsse_subscriptions(id) on delete cascade, -- Subscription is included so that records are deleted when a subscription is removed
primary key(label,article)
) without rowid;
-- set version marker
pragma user_version = 2;
insert into arsse_meta(key,value) values('schema_version','2');