mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Clean up user driver API
- It is no longer assumed a driver knows whether a user exists - The $password param is now required (but nullable when setting
This commit is contained in:
parent
771f79323c
commit
5a17efc7b5
10 changed files with 55 additions and 80 deletions
|
@ -13,7 +13,7 @@ use JKingWeb\Arsse\User\Exception as UserException;
|
||||||
|
|
||||||
abstract class AbstractImportExport {
|
abstract class AbstractImportExport {
|
||||||
public function import(string $user, string $data, bool $flat = false, bool $replace = false): bool {
|
public function import(string $user, string $data, bool $flat = false, bool $replace = false): bool {
|
||||||
if (!Arsse::$user->exists($user)) {
|
if (!Arsse::$db->userExists($user)) {
|
||||||
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
// first extract useful information from the input
|
// first extract useful information from the input
|
||||||
|
|
|
@ -91,7 +91,7 @@ class OPML extends AbstractImportExport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function export(string $user, bool $flat = false): string {
|
public function export(string $user, bool $flat = false): string {
|
||||||
if (!Arsse::$user->exists($user)) {
|
if (!Arsse::$db->userExists($user)) {
|
||||||
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
throw new UserException("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
}
|
}
|
||||||
$tags = [];
|
$tags = [];
|
||||||
|
|
|
@ -48,10 +48,6 @@ class User {
|
||||||
return $this->u->userList();
|
return $this->u->userList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exists(string $user): bool {
|
|
||||||
return $this->u->userExists($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function add($user, $password = null): string {
|
public function add($user, $password = null): string {
|
||||||
return $this->u->userAdd($user, $password) ?? $this->u->userAdd($user, $this->generatePassword());
|
return $this->u->userAdd($user, $password) ?? $this->u->userAdd($user, $this->generatePassword());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,26 +7,49 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\User;
|
namespace JKingWeb\Arsse\User;
|
||||||
|
|
||||||
interface Driver {
|
interface Driver {
|
||||||
public const FUNC_NOT_IMPLEMENTED = 0;
|
|
||||||
public const FUNC_INTERNAL = 1;
|
|
||||||
public const FUNC_EXTERNAL = 2;
|
|
||||||
|
|
||||||
// returns an instance of a class implementing this interface.
|
|
||||||
public function __construct();
|
public function __construct();
|
||||||
// returns a human-friendly name for the driver (for display in installer, for example)
|
|
||||||
|
/** Returns a human-friendly name for the driver (for display in installer, for example) */
|
||||||
public static function driverName(): string;
|
public static function driverName(): string;
|
||||||
// authenticates a user against their name and password
|
|
||||||
|
/** Authenticates a user against their name and password */
|
||||||
public function auth(string $user, string $password): bool;
|
public function auth(string $user, string $password): bool;
|
||||||
// checks whether a user exists
|
|
||||||
public function userExists(string $user): bool;
|
/** Adds a new user and returns their password
|
||||||
// adds a user
|
*
|
||||||
public function userAdd(string $user, string $password = null);
|
* When given no password the implementation may return null; the user
|
||||||
// removes a user
|
* manager will then generate a random password and try again with that
|
||||||
|
* password. Alternatively the implementation may generate its own
|
||||||
|
* password if desired
|
||||||
|
*
|
||||||
|
* @param string $user The username to create
|
||||||
|
* @param string|null $password The cleartext password to assign to the user, or null to generate a random password
|
||||||
|
*/
|
||||||
|
public function userAdd(string $user, string $password = null): ?string;
|
||||||
|
|
||||||
|
/** Removes a user */
|
||||||
public function userRemove(string $user): bool;
|
public function userRemove(string $user): bool;
|
||||||
// lists all users
|
|
||||||
|
/** Lists all users */
|
||||||
public function userList(): array;
|
public function userList(): array;
|
||||||
// sets a user's password; if the driver does not require the old password, it may be ignored
|
|
||||||
public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null);
|
/** sets a user's password
|
||||||
// removes a user's password; this makes authentication fail unconditionally
|
*
|
||||||
|
* When given no password the implementation may return null; the user
|
||||||
|
* manager will then generate a random password and try again with that
|
||||||
|
* password. Alternatively the implementation may generate its own
|
||||||
|
* password if desired
|
||||||
|
*
|
||||||
|
* @param string $user The user for whom to change the password
|
||||||
|
* @param string|null $password The cleartext password to assign to the user, or null to generate a random password
|
||||||
|
* @param string|null $oldPassword The user's previous password, if known
|
||||||
|
*/
|
||||||
|
public function userPasswordSet(string $user, ?string $newPassword, string $oldPassword = null);
|
||||||
|
|
||||||
|
/** removes a user's password; this makes authentication fail unconditionally
|
||||||
|
*
|
||||||
|
* @param string $user The user for whom to change the password
|
||||||
|
* @param string|null $oldPassword The user's previous password, if known
|
||||||
|
*/
|
||||||
public function userPasswordUnset(string $user, string $oldPassword = null): bool;
|
public function userPasswordUnset(string $user, string $oldPassword = null): bool;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?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\User;
|
|
||||||
|
|
||||||
class ExceptionAuthz extends Exception {
|
|
||||||
}
|
|
|
@ -32,10 +32,6 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
return password_verify($password, $hash);
|
return password_verify($password, $hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userExists(string $user): bool {
|
|
||||||
return Arsse::$db->userExists($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function userAdd(string $user, string $password = null): ?string {
|
public function userAdd(string $user, string $password = null): ?string {
|
||||||
if (isset($password)) {
|
if (isset($password)) {
|
||||||
// only add the user if the password is not null; the user manager will retry with a generated password if null is returned
|
// only add the user if the password is not null; the user manager will retry with a generated password if null is returned
|
||||||
|
@ -52,7 +48,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
return Arsse::$db->userList();
|
return Arsse::$db->userList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): ?string {
|
public function userPasswordSet(string $user, ?string $newPassword, string $oldPassword = null): ?string {
|
||||||
// do nothing: the internal database is updated regardless of what the driver does (assuming it does not throw an exception)
|
// do nothing: the internal database is updated regardless of what the driver does (assuming it does not throw an exception)
|
||||||
return $newPassword;
|
return $newPassword;
|
||||||
}
|
}
|
||||||
|
@ -70,4 +66,8 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
protected function userPasswordGet(string $user): ?string {
|
protected function userPasswordGet(string $user): ?string {
|
||||||
return Arsse::$db->userPasswordGet($user);
|
return Arsse::$db->userPasswordGet($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function userExists(string $user): bool {
|
||||||
|
return Arsse::$db->userExists($user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
// create a mock user manager
|
// create a mock user manager
|
||||||
Arsse::$user = \Phake::mock(\JKingWeb\Arsse\User::class);
|
Arsse::$user = \Phake::mock(\JKingWeb\Arsse\User::class);
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(true);
|
|
||||||
// create a mock Import/Export processor
|
// create a mock Import/Export processor
|
||||||
$this->proc = \Phake::partialMock(AbstractImportExport::class);
|
$this->proc = \Phake::partialMock(AbstractImportExport::class);
|
||||||
// initialize an SQLite memeory database
|
// initialize an SQLite memeory database
|
||||||
|
@ -147,9 +146,8 @@ class TestImportExport extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportForAMissingUser(): void {
|
public function testImportForAMissingUser(): void {
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
$this->proc->import("john.doe@example.com", "", false, false);
|
$this->proc->import("no.one@example.com", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testImportWithInvalidFolder(): void {
|
public function testImportWithInvalidFolder(): void {
|
||||||
|
|
|
@ -82,8 +82,7 @@ OPML_EXPORT_SERIALIZATION;
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
self::clearData();
|
self::clearData();
|
||||||
Arsse::$db = \Phake::mock(\JKingWeb\Arsse\Database::class);
|
Arsse::$db = \Phake::mock(\JKingWeb\Arsse\Database::class);
|
||||||
Arsse::$user = \Phake::mock(\JKingWeb\Arsse\User::class);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToOpml(): void {
|
public function testExportToOpml(): void {
|
||||||
|
@ -101,7 +100,7 @@ OPML_EXPORT_SERIALIZATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportToOpmlAMissingUser(): void {
|
public function testExportToOpmlAMissingUser(): void {
|
||||||
\Phake::when(Arsse::$user)->exists->thenReturn(false);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
(new OPML)->export("john.doe@example.com");
|
(new OPML)->export("john.doe@example.com");
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,18 +75,6 @@ class TestInternal extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verify(Arsse::$db, \Phake::times(2))->userList;
|
\Phake::verify(Arsse::$db, \Phake::times(2))->userList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCheckThatAUserExists(): void {
|
|
||||||
$john = "john.doe@example.com";
|
|
||||||
$jane = "jane.doe@example.com";
|
|
||||||
\Phake::when(Arsse::$db)->userExists($john)->thenReturn(true);
|
|
||||||
\Phake::when(Arsse::$db)->userExists($jane)->thenReturn(false);
|
|
||||||
$driver = new Driver;
|
|
||||||
$this->assertTrue($driver->userExists($john));
|
|
||||||
\Phake::verify(Arsse::$db)->userExists($john);
|
|
||||||
$this->assertFalse($driver->userExists($jane));
|
|
||||||
\Phake::verify(Arsse::$db)->userExists($jane);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testAddAUser(): void {
|
public function testAddAUser(): void {
|
||||||
$john = "john.doe@example.com";
|
$john = "john.doe@example.com";
|
||||||
\Phake::when(Arsse::$db)->userAdd->thenReturnCallback(function($user, $pass) {
|
\Phake::when(Arsse::$db)->userAdd->thenReturnCallback(function($user, $pass) {
|
||||||
|
@ -119,20 +107,18 @@ class TestInternal extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verifyNoFurtherInteraction(Arsse::$db);
|
\Phake::verifyNoFurtherInteraction(Arsse::$db);
|
||||||
$this->assertSame("superman", (new Driver)->userPasswordSet($john, "superman"));
|
$this->assertSame("superman", (new Driver)->userPasswordSet($john, "superman"));
|
||||||
$this->assertSame(null, (new Driver)->userPasswordSet($john, null));
|
$this->assertSame(null, (new Driver)->userPasswordSet($john, null));
|
||||||
|
\Phake::verify(Arsse::$db, \Phake::times(0))->userPasswordSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnsetAPassword(): void {
|
public function testUnsetAPassword(): void {
|
||||||
$drv = \Phake::partialMock(Driver::class);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
\Phake::when($drv)->userExists->thenReturn(true);
|
$this->assertTrue((new Driver)->userPasswordUnset("john.doe@example.com"));
|
||||||
\Phake::verifyNoFurtherInteraction(Arsse::$db);
|
\Phake::verify(Arsse::$db, \Phake::times(0))->userPasswordUnset;
|
||||||
$this->assertTrue($drv->userPasswordUnset("john.doe@example.com"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnsetAPasswordForAMssingUser(): void {
|
public function testUnsetAPasswordForAMssingUser(): void {
|
||||||
$drv = \Phake::partialMock(Driver::class);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
\Phake::when($drv)->userExists->thenReturn(false);
|
|
||||||
\Phake::verifyNoFurtherInteraction(Arsse::$db);
|
|
||||||
$this->assertException("doesNotExist", "User");
|
$this->assertException("doesNotExist", "User");
|
||||||
$drv->userPasswordUnset("john.doe@example.com");
|
(new Driver)->userPasswordUnset("john.doe@example.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,23 +83,6 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider provideExistence */
|
|
||||||
public function testCheckThatAUserExists(string $user, $exp): void {
|
|
||||||
$u = new User($this->drv);
|
|
||||||
\Phake::when($this->drv)->userExists("john.doe@example.com")->thenReturn(true);
|
|
||||||
\Phake::when($this->drv)->userExists("jane.doe@example.com")->thenReturn(false);
|
|
||||||
$this->assertSame($exp, $u->exists($user));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideExistence(): iterable {
|
|
||||||
$john = "john.doe@example.com";
|
|
||||||
$jane = "jane.doe@example.com";
|
|
||||||
return [
|
|
||||||
[$john, true],
|
|
||||||
[$jane, false],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @dataProvider provideAdditions */
|
/** @dataProvider provideAdditions */
|
||||||
public function testAddAUser(string $user, $password, $exp): void {
|
public function testAddAUser(string $user, $password, $exp): void {
|
||||||
$u = new User($this->drv);
|
$u = new User($this->drv);
|
||||||
|
|
Loading…
Reference in a new issue