mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Add backend functionality to rename users
This commit is contained in:
parent
88cf3c6dae
commit
5ec04d33c6
7 changed files with 164 additions and 8 deletions
|
@ -273,6 +273,20 @@ class Database {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function userRename(string $user, string $name): bool {
|
||||||
|
if ($user === $name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!$this->db->prepare("UPDATE arsse_users set id = ? where id = ?", "str", "str")->run($name, $user)->changes()) {
|
||||||
|
throw new User\ExceptionConflict("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
|
||||||
|
}
|
||||||
|
} catch (Db\ExceptionInput $e) {
|
||||||
|
throw new User\ExceptionConflict("alreadyExists", ["action" => __FUNCTION__, "user" => $name], $e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** Removes a user from the database */
|
/** Removes a user from the database */
|
||||||
public function userRemove(string $user): bool {
|
public function userRemove(string $user): bool {
|
||||||
if ($this->db->prepare("DELETE from arsse_users where id = ?", "str")->run($user)->changes() < 1) {
|
if ($this->db->prepare("DELETE from arsse_users where id = ?", "str")->run($user)->changes() < 1) {
|
||||||
|
|
27
lib/User.php
27
lib/User.php
|
@ -42,6 +42,21 @@ class User {
|
||||||
return (string) $this->id;
|
return (string) $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function begin(): Db\Transaction {
|
||||||
|
/* TODO: A proper implementation of this would return a meta-transaction
|
||||||
|
object which would contain both a user-manager transaction (when
|
||||||
|
applicable) and a database transaction, and commit or roll back both
|
||||||
|
as the situation calls.
|
||||||
|
|
||||||
|
In theory, an external user driver would probably have to implement its
|
||||||
|
own approximation of atomic transactions and rollback. In practice the
|
||||||
|
only driver is the internal one, which is always backed by an ACID
|
||||||
|
database; the added complexity is thus being deferred until such time
|
||||||
|
as it is actually needed for a concrete implementation.
|
||||||
|
*/
|
||||||
|
return Arsse::$db->begin();
|
||||||
|
}
|
||||||
|
|
||||||
public function auth(string $user, string $password): bool {
|
public function auth(string $user, string $password): bool {
|
||||||
$prevUser = $this->id;
|
$prevUser = $this->id;
|
||||||
$this->id = $user;
|
$this->id = $user;
|
||||||
|
@ -89,6 +104,18 @@ class User {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function rename(string $user, string $newName): bool {
|
||||||
|
if ($this->u->userRename($user, $newName)) {
|
||||||
|
if (!Arsse::$db->userExists($user)) {
|
||||||
|
Arsse::$db->userAdd($newName, null);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return Arsse::$db->userRename($user, $newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function remove(string $user): bool {
|
public function remove(string $user): bool {
|
||||||
try {
|
try {
|
||||||
$out = $this->u->userRemove($user);
|
$out = $this->u->userRemove($user);
|
||||||
|
|
|
@ -27,6 +27,13 @@ interface Driver {
|
||||||
*/
|
*/
|
||||||
public function userAdd(string $user, string $password = null): ?string;
|
public function userAdd(string $user, string $password = null): ?string;
|
||||||
|
|
||||||
|
/** Renames a user
|
||||||
|
*
|
||||||
|
* The implementation must retain all user metadata as well as the
|
||||||
|
* user's password
|
||||||
|
*/
|
||||||
|
public function userRename(string $user, string $newName): bool;
|
||||||
|
|
||||||
/** Removes a user */
|
/** Removes a user */
|
||||||
public function userRemove(string $user): bool;
|
public function userRemove(string $user): bool;
|
||||||
|
|
||||||
|
@ -44,7 +51,7 @@ interface Driver {
|
||||||
* @param string|null $password The cleartext password to assign to the user, or null to generate a random 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
|
* @param string|null $oldPassword The user's previous password, if known
|
||||||
*/
|
*/
|
||||||
public function userPasswordSet(string $user, ?string $newPassword, string $oldPassword = null);
|
public function userPasswordSet(string $user, ?string $newPassword, string $oldPassword = null): ?string;
|
||||||
|
|
||||||
/** Removes a user's password; this makes authentication fail unconditionally
|
/** Removes a user's password; this makes authentication fail unconditionally
|
||||||
*
|
*
|
||||||
|
|
|
@ -40,6 +40,16 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
return $password;
|
return $password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function userRename(string $user, string $newName): bool {
|
||||||
|
// do nothing: the internal database is updated regardless of what the driver does (assuming it does not throw an exception)
|
||||||
|
// throw an exception if the user does not exist
|
||||||
|
if (!$this->userExists($user)) {
|
||||||
|
throw new ExceptionConflict("doesNotExist", ['action' => __FUNCTION__, 'user' => $user]);
|
||||||
|
} else {
|
||||||
|
return !($user === $newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function userRemove(string $user): bool {
|
public function userRemove(string $user): bool {
|
||||||
return Arsse::$db->userRemove($user);
|
return Arsse::$db->userRemove($user);
|
||||||
}
|
}
|
||||||
|
@ -50,14 +60,19 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
|
|
||||||
public function userPasswordSet(string $user, ?string $newPassword, 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;
|
// throw an exception if the user does not exist
|
||||||
|
if (!$this->userExists($user)) {
|
||||||
|
throw new ExceptionConflict("doesNotExist", ['action' => __FUNCTION__, 'user' => $user]);
|
||||||
|
} else {
|
||||||
|
return $newPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userPasswordUnset(string $user, string $oldPassword = null): bool {
|
public function userPasswordUnset(string $user, string $oldPassword = null): bool {
|
||||||
// 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)
|
||||||
// throw an exception if the user does not exist
|
// throw an exception if the user does not exist
|
||||||
if (!$this->userExists($user)) {
|
if (!$this->userExists($user)) {
|
||||||
throw new ExceptionConflict("doesNotExist", ['action' => "userPasswordUnset", 'user' => $user]);
|
throw new ExceptionConflict("doesNotExist", ['action' => __FUNCTION__, 'user' => $user]);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +89,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
public function userPropertiesGet(string $user, bool $includeLarge = true): array {
|
public function userPropertiesGet(string $user, bool $includeLarge = true): array {
|
||||||
// do nothing: the internal database will retrieve everything for us
|
// do nothing: the internal database will retrieve everything for us
|
||||||
if (!$this->userExists($user)) {
|
if (!$this->userExists($user)) {
|
||||||
throw new ExceptionConflict("doesNotExist", ['action' => "userPasswordUnset", 'user' => $user]);
|
throw new ExceptionConflict("doesNotExist", ['action' => __FUNCTION__, 'user' => $user]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -83,7 +98,7 @@ class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||||
public function userPropertiesSet(string $user, array $data): array {
|
public function userPropertiesSet(string $user, array $data): array {
|
||||||
// do nothing: the internal database will set everything for us
|
// do nothing: the internal database will set everything for us
|
||||||
if (!$this->userExists($user)) {
|
if (!$this->userExists($user)) {
|
||||||
throw new ExceptionConflict("doesNotExist", ['action' => "userPasswordUnset", 'user' => $user]);
|
throw new ExceptionConflict("doesNotExist", ['action' => __FUNCTION__, 'user' => $user]);
|
||||||
} else {
|
} else {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,4 +180,29 @@ trait SeriesUser {
|
||||||
$this->assertException("doesNotExist", "User", "ExceptionConflict");
|
$this->assertException("doesNotExist", "User", "ExceptionConflict");
|
||||||
Arsse::$db->userLookup(2112);
|
Arsse::$db->userLookup(2112);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRenameAUser(): void {
|
||||||
|
$this->assertTrue(Arsse::$db->userRename("john.doe@example.com", "juan.doe@example.com"));
|
||||||
|
$state = $this->primeExpectations($this->data, [
|
||||||
|
'arsse_users' => ['id', 'num'],
|
||||||
|
'arsse_user_meta' => ["owner", "key", "value"]
|
||||||
|
]);
|
||||||
|
$state['arsse_users']['rows'][2][0] = "juan.doe@example.com";
|
||||||
|
$state['arsse_user_meta']['rows'][6][0] = "juan.doe@example.com";
|
||||||
|
$this->compareExpectations(static::$drv, $state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAUserToTheSameName(): void {
|
||||||
|
$this->assertFalse(Arsse::$db->userRename("john.doe@example.com", "john.doe@example.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAMissingUser(): void {
|
||||||
|
$this->assertException("doesNotExist", "User", "ExceptionConflict");
|
||||||
|
Arsse::$db->userRename("juan.doe@example.com", "john.doe@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAUserToADuplicateName(): void {
|
||||||
|
$this->assertException("alreadyExists", "User", "ExceptionConflict");
|
||||||
|
Arsse::$db->userRename("john.doe@example.com", "jane.doe@example.com");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace JKingWeb\Arsse\TestCase\User;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\User\Driver as DriverInterface;
|
use JKingWeb\Arsse\User\Driver as DriverInterface;
|
||||||
|
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||||
use JKingWeb\Arsse\User\Internal\Driver;
|
use JKingWeb\Arsse\User\Internal\Driver;
|
||||||
|
|
||||||
/** @covers \JKingWeb\Arsse\User\Internal\Driver */
|
/** @covers \JKingWeb\Arsse\User\Internal\Driver */
|
||||||
|
@ -88,6 +89,21 @@ class TestInternal extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verify(Arsse::$db)->userAdd;
|
\Phake::verify(Arsse::$db)->userAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRenameAUser(): void {
|
||||||
|
$john = "john.doe@example.com";
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
|
$this->assertTrue((new Driver)->userRename($john, "jane.doe@example.com"));
|
||||||
|
$this->assertFalse((new Driver)->userRename($john, $john));
|
||||||
|
\Phake::verify(Arsse::$db, \Phake::times(2))->userExists($john);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAMissingUser(): void {
|
||||||
|
$john = "john.doe@example.com";
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
|
$this->assertException("doesNotExist", "User", "ExceptionConflict");
|
||||||
|
(new Driver)->userRename($john, "jane.doe@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
public function testRemoveAUser(): void {
|
public function testRemoveAUser(): void {
|
||||||
$john = "john.doe@example.com";
|
$john = "john.doe@example.com";
|
||||||
\Phake::when(Arsse::$db)->userRemove->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\User\ExceptionConflict("doesNotExist"));
|
\Phake::when(Arsse::$db)->userRemove->thenReturn(true)->thenThrow(new \JKingWeb\Arsse\User\ExceptionConflict("doesNotExist"));
|
||||||
|
@ -104,12 +120,18 @@ class TestInternal extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
public function testSetAPassword(): void {
|
public function testSetAPassword(): void {
|
||||||
$john = "john.doe@example.com";
|
$john = "john.doe@example.com";
|
||||||
\Phake::verifyNoFurtherInteraction(Arsse::$db);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
$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;
|
\Phake::verify(Arsse::$db, \Phake::times(0))->userPasswordSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSetAPasswordForAMssingUser(): void {
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
|
$this->assertException("doesNotExist", "User", "ExceptionConflict");
|
||||||
|
(new Driver)->userPasswordSet("john.doe@example.com", "secret");
|
||||||
|
}
|
||||||
|
|
||||||
public function testUnsetAPassword(): void {
|
public function testUnsetAPassword(): void {
|
||||||
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
$this->assertTrue((new Driver)->userPasswordUnset("john.doe@example.com"));
|
$this->assertTrue((new Driver)->userPasswordUnset("john.doe@example.com"));
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace JKingWeb\Arsse\TestCase\User;
|
||||||
use JKingWeb\Arsse\Arsse;
|
use JKingWeb\Arsse\Arsse;
|
||||||
use JKingWeb\Arsse\Database;
|
use JKingWeb\Arsse\Database;
|
||||||
use JKingWeb\Arsse\User;
|
use JKingWeb\Arsse\User;
|
||||||
|
use JKingWeb\Arsse\Db\Transaction;
|
||||||
use JKingWeb\Arsse\User\ExceptionConflict;
|
use JKingWeb\Arsse\User\ExceptionConflict;
|
||||||
use JKingWeb\Arsse\User\ExceptionInput;
|
use JKingWeb\Arsse\User\ExceptionInput;
|
||||||
use JKingWeb\Arsse\User\Driver;
|
use JKingWeb\Arsse\User\Driver;
|
||||||
|
@ -43,6 +44,13 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
$this->assertSame("", (string) $u);
|
$this->assertSame("", (string) $u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testStartATransaction(): void {
|
||||||
|
\Phake::when(Arsse::$db)->begin->thenReturn(\Phake::mock(Transaction::class));
|
||||||
|
$u = new User($this->drv);
|
||||||
|
$this->assertInstanceOf(Transaction::class, $u->begin());
|
||||||
|
\Phake::verify(Arsse::$db)->begin();
|
||||||
|
}
|
||||||
|
|
||||||
public function testGeneratePasswords(): void {
|
public function testGeneratePasswords(): void {
|
||||||
$u = new User($this->drv);
|
$u = new User($this->drv);
|
||||||
$pass1 = $u->generatePassword();
|
$pass1 = $u->generatePassword();
|
||||||
|
@ -174,9 +182,48 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
\Phake::verify(Arsse::$db)->userExists($user);
|
\Phake::verify(Arsse::$db)->userExists($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRenameAUser(): void {
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
|
\Phake::when(Arsse::$db)->userAdd->thenReturn(true);
|
||||||
|
\Phake::when(Arsse::$db)->userRename->thenReturn(true);
|
||||||
|
\Phake::when($this->drv)->userRename->thenReturn(true);
|
||||||
|
$u = new User($this->drv);
|
||||||
|
$old = "john.doe@example.com";
|
||||||
|
$new = "jane.doe@example.com";
|
||||||
|
$this->assertTrue($u->rename($old, $new));
|
||||||
|
\Phake::verify($this->drv)->userRename($old, $new);
|
||||||
|
\Phake::verify(Arsse::$db)->userExists($old);
|
||||||
|
\Phake::verify(Arsse::$db)->userRename($old, $new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAUserWeDoNotKnow(): void {
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
|
\Phake::when(Arsse::$db)->userAdd->thenReturn(true);
|
||||||
|
\Phake::when(Arsse::$db)->userRename->thenReturn(true);
|
||||||
|
\Phake::when($this->drv)->userRename->thenReturn(true);
|
||||||
|
$u = new User($this->drv);
|
||||||
|
$old = "john.doe@example.com";
|
||||||
|
$new = "jane.doe@example.com";
|
||||||
|
$this->assertTrue($u->rename($old, $new));
|
||||||
|
\Phake::verify($this->drv)->userRename($old, $new);
|
||||||
|
\Phake::verify(Arsse::$db)->userExists($old);
|
||||||
|
\Phake::verify(Arsse::$db)->userAdd($new, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameAUserWithoutEffect(): void {
|
||||||
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
|
\Phake::when(Arsse::$db)->userAdd->thenReturn(true);
|
||||||
|
\Phake::when(Arsse::$db)->userRename->thenReturn(true);
|
||||||
|
\Phake::when($this->drv)->userRename->thenReturn(false);
|
||||||
|
$u = new User($this->drv);
|
||||||
|
$old = "john.doe@example.com";
|
||||||
|
$new = "jane.doe@example.com";
|
||||||
|
$this->assertFalse($u->rename($old, $old));
|
||||||
|
\Phake::verify($this->drv)->userRename($old, $old);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRemoveAUser(): void {
|
public function testRemoveAUser(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$pass = "secret";
|
|
||||||
$u = new User($this->drv);
|
$u = new User($this->drv);
|
||||||
\Phake::when($this->drv)->userRemove->thenReturn(true);
|
\Phake::when($this->drv)->userRemove->thenReturn(true);
|
||||||
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(true);
|
||||||
|
@ -188,7 +235,6 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
|
|
||||||
public function testRemoveAUserWeDoNotKnow(): void {
|
public function testRemoveAUserWeDoNotKnow(): void {
|
||||||
$user = "john.doe@example.com";
|
$user = "john.doe@example.com";
|
||||||
$pass = "secret";
|
|
||||||
$u = new User($this->drv);
|
$u = new User($this->drv);
|
||||||
\Phake::when($this->drv)->userRemove->thenReturn(true);
|
\Phake::when($this->drv)->userRemove->thenReturn(true);
|
||||||
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
\Phake::when(Arsse::$db)->userExists->thenReturn(false);
|
||||||
|
|
Loading…
Reference in a new issue