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

Implement Miniflux user creation

This commit is contained in:
J. King 2020-12-31 13:57:36 -05:00
parent ee0c3c9449
commit 197922f92f
3 changed files with 111 additions and 20 deletions

View file

@ -333,6 +333,33 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return $out; return $out;
} }
protected function editUser(string $user, array $data): array {
// map Miniflux properties to internal metadata properties
$in = [];
foreach (self::USER_META_MAP as $i => [$o,,]) {
if (isset($data[$i])) {
if ($i === "entry_sorting_direction") {
$in[$o] = $data[$i] === "asc";
} else {
$in[$o] = $data[$i];
}
}
}
// make any requested changes
$tr = Arsse::$user->begin();
if ($in) {
Arsse::$user->propertiesSet($user, $in);
}
// read out the newly-modified user and commit the changes
$out = $this->listUsers([$user], true)[0];
$tr->commit();
// add the input password if a password change was requested
if (isset($data['password'])) {
$out['password'] = $data['password'];
}
return $out;
}
protected function discoverSubscriptions(array $data): ResponseInterface { protected function discoverSubscriptions(array $data): ResponseInterface {
try { try {
$list = Feed::discoverAll((string) $data['url'], (string) $data['username'], (string) $data['password']); $list = Feed::discoverAll((string) $data['url'], (string) $data['username'], (string) $data['password']);
@ -378,6 +405,33 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass); return new Response($this->listUsers([Arsse::$user->id], false)[0] ?? new \stdClass);
} }
protected function createUser(array $data): ResponseInterface {
if ($data['username'] === null) {
return new ErrorResponse(["MissingInputValue", 'field' => "username"], 422);
} elseif ($data['password'] === null) {
return new ErrorResponse(["MissingInputValue", 'field' => "password"], 422);
}
try {
$tr = Arsse::$user->begin();
$data['password'] = Arsse::$user->add($data['username'], $data['password']);
$out = $this->editUser($data['username'], $data);
$tr->commit();
} catch (UserException $e) {
switch ($e->getCode()) {
case 10403:
return new ErrorResponse(["DuplicateUser", 'user' => $data['username']], 409);
case 10441:
return new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422);
case 10443:
return new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422);
case 10444:
return new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422);
}
throw $e; // @codeCoverageIgnore
}
return new Response($out, 201);
}
protected function updateUserByNum(array $path, array $data): ResponseInterface { protected function updateUserByNum(array $path, array $data): ResponseInterface {
// this function is restricted to admins unless the affected user and calling user are the same // this function is restricted to admins unless the affected user and calling user are the same
$user = Arsse::$user->propertiesGet(Arsse::$user->id, false); $user = Arsse::$user->propertiesGet(Arsse::$user->id, false);
@ -396,17 +450,6 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return new ErrorResponse("404", 404); return new ErrorResponse("404", 404);
} }
} }
// map Miniflux properties to internal metadata properties
$in = [];
foreach (self::USER_META_MAP as $i => [$o,,]) {
if (isset($data[$i])) {
if ($i === "entry_sorting_direction") {
$in[$o] = $data[$i] === "asc";
} else {
$in[$o] = $data[$i];
}
}
}
// make any requested changes // make any requested changes
try { try {
$tr = Arsse::$user->begin(); $tr = Arsse::$user->begin();
@ -417,11 +460,7 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
if (isset($data['password'])) { if (isset($data['password'])) {
Arsse::$user->passwordSet($user, $data['password']); Arsse::$user->passwordSet($user, $data['password']);
} }
if ($in) { $out = $this->editUser($user, $data);
Arsse::$user->propertiesSet($user, $in);
}
// read out the newly-modified user and commit the changes
$out = $this->listUsers([$user], true)[0];
$tr->commit(); $tr->commit();
} catch (UserException $e) { } catch (UserException $e) {
switch ($e->getCode()) { switch ($e->getCode()) {
@ -436,10 +475,6 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
throw $e; // @codeCoverageIgnore throw $e; // @codeCoverageIgnore
} }
// add the input password if a password change was requested
if (isset($data['password'])) {
$out['password'] = $data['password'];
}
return new Response($out); return new Response($out);
} }

View file

@ -11,6 +11,7 @@ return [
'API.Miniflux.Error.401' => 'Access Unauthorized', 'API.Miniflux.Error.401' => 'Access Unauthorized',
'API.Miniflux.Error.403' => 'Access Forbidden', 'API.Miniflux.Error.403' => 'Access Forbidden',
'API.Miniflux.Error.404' => 'Resource Not Found', 'API.Miniflux.Error.404' => 'Resource Not Found',
'API.Miniflux.Error.MissingInputValue' => 'Required key "{field}" was not present in input',
'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}', 'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}',
'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}', 'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}',
'API.Miniflux.Error.InvalidInputValue' => 'Supplied value is not valid for input key "{field}"', 'API.Miniflux.Error.InvalidInputValue' => 'Supplied value is not valid for input key "{field}"',

View file

@ -321,6 +321,61 @@ class TestV1 extends \JKingWeb\Arsse\Test\AbstractTest {
]; ];
} }
/** @dataProvider provideUserAdditions */
public function testAddAUser(array $body, $in1, $out1, $in2, $out2, ResponseInterface $exp): void {
$this->h = $this->createPartialMock(V1::class, ["now"]);
$this->h->method("now")->willReturn(Date::normalize(self::NOW));
Arsse::$user = $this->createMock(User::class);
Arsse::$user->method("begin")->willReturn($this->transaction);
Arsse::$user->method("propertiesGet")->willReturnCallback(function(string $u, bool $includeLarge) {
if ($u === "john.doe@example.com") {
return ['num' => 1, 'admin' => true];
} else {
return ['num' => 2, 'admin' => false];
}
});
if ($out1 instanceof \Exception) {
Arsse::$user->method("add")->willThrowException($out1);
} else {
Arsse::$user->method("add")->willReturn($in1[1] ?? "");
}
if ($out2 instanceof \Exception) {
Arsse::$user->method("propertiesSet")->willThrowException($out2);
} else {
Arsse::$user->method("propertiesSet")->willReturn($out2 ?? []);
}
if ($in1 === null) {
Arsse::$user->expects($this->exactly(0))->method("add");
} else {
Arsse::$user->expects($this->exactly(1))->method("add")->with(...($in1 ?? []));
}
if ($in2 === null) {
Arsse::$user->expects($this->exactly(0))->method("propertiesSet");
} else {
Arsse::$user->expects($this->exactly(1))->method("propertiesSet")->with($body['username'], $in2);
}
$this->assertMessage($exp, $this->req("POST", "/users", $body));
}
public function provideUserAdditions(): iterable {
$resp1 = array_merge($this->users[1], ['username' => "ook", 'password' => "eek"]);
return [
[[], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "username"], 422)],
[['username' => "ook"], null, null, null, null, new ErrorResponse(["MissingInputValue", 'field' => "password"], 422)],
[['username' => "ook", 'password' => "eek"], ["ook", "eek"], new ExceptionConflict("alreadyExists"), null, null, new ErrorResponse(["DuplicateUser", 'user' => "ook"], 409)],
[['username' => "j:k", 'password' => "eek"], ["j:k", "eek"], new UserExceptionInput("invalidUsername"), null, null, new ErrorResponse(["InvalidInputValue", 'field' => "username"], 422)],
[['username' => "ook", 'password' => "eek", 'timezone' => "ook"], ["ook", "eek"], "eek", ['tz' => "ook"], new UserExceptionInput("invalidTimezone"), new ErrorResponse(["InvalidInputValue", 'field' => "timezone"], 422)],
[['username' => "ook", 'password' => "eek", 'entries_per_page' => -1], ["ook", "eek"], "eek", ['page_size' => -1], new UserExceptionInput("invalidNonZeroInteger"), new ErrorResponse(["InvalidInputValue", 'field' => "entries_per_page"], 422)],
[['username' => "ook", 'password' => "eek", 'theme' => "default"], ["ook", "eek"], "eek", ['theme' => "default"], ['theme' => "default"], new Response($resp1, 201)],
];
}
public function testAddAUserWithoutAuthority(): void {
Arsse::$user = $this->createMock(User::class);
Arsse::$user->method("propertiesGet")->willReturn(['num' => 1, 'admin' => false]);
$this->assertMessage(new ErrorResponse("403", 403), $this->req("POST", "/users", []));
}
public function testListCategories(): void { public function testListCategories(): void {
\Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([ \Phake::when(Arsse::$db)->folderList->thenReturn(new Result($this->v([
['id' => 1, 'name' => "Science"], ['id' => 1, 'name' => "Science"],