diff --git a/README.md b/README.md
index 7fd86cef..54a87197 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,8 @@ As a general rule, The Arsse should yield the same output as the reference imple
- When marking articles as starred the feed ID is ignored, as they are not needed to establish uniqueness
- The feed updater ignores the `userId` parameter: feeds in The Arsse are deduplicated, and have no owner
- The `/feeds/all` route lists only feeds which should be checked for updates, and it also returns all `userId` attributes as empty strings: feeds in The Arsse are deduplicated, and have no owner
-- The updater console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required NextCloud subsystems
+- The API's "updater" routes do not require administrator priviledges as The Arsse has no concept of user classes
+- The "updater" console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required NextCloud subsystems
- The `lastLoginTimestamp` attribute of the user metadata is always the current time: The Arsse's implementation of the protocol is fully stateless
#### Ambiguities
diff --git a/lib/CLI.php b/lib/CLI.php
index 1605efe4..b116d1a2 100644
--- a/lib/CLI.php
+++ b/lib/CLI.php
@@ -38,8 +38,6 @@ USAGE_TEXT;
protected function loadConf(): bool {
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
Arsse::load($conf);
- // command-line operations will never respect authorization
- Arsse::$user->authorizationEnabled(false);
return true;
}
diff --git a/lib/Database.php b/lib/Database.php
index b615a275..03300502 100644
--- a/lib/Database.php
+++ b/lib/Database.php
@@ -149,24 +149,13 @@ class Database {
return true;
}
- public function userList(string $domain = null): array {
+ public function userList(): array {
$out = [];
- if ($domain !== null) {
- if (!Arsse::$user->authorize("@".$domain, __FUNCTION__)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $domain]);
- }
- $domain = str_replace(["\\","%","_"], ["\\\\", "\\%", "\\_"], $domain);
- $domain = "%@".$domain;
- foreach ($this->db->prepare("SELECT id from arsse_users where id like ?", "str")->run($domain) as $user) {
- $out[] = $user['id'];
- }
- } else {
- if (!Arsse::$user->authorize("", __FUNCTION__)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => "global"]);
- }
- foreach ($this->db->query("SELECT id from arsse_users") as $user) {
- $out[] = $user['id'];
- }
+ if (!Arsse::$user->authorize("", __FUNCTION__)) {
+ throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => ""]);
+ }
+ foreach ($this->db->query("SELECT id from arsse_users") as $user) {
+ $out[] = $user['id'];
}
return $out;
}
@@ -197,52 +186,6 @@ class Database {
return $password;
}
- public function userPropertiesGet(string $user): array {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- $prop = $this->db->prepare("SELECT name,rights from arsse_users where id = ?", "str")->run($user)->getRow();
- if (!$prop) {
- throw new User\Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return $prop;
- }
-
- public function userPropertiesSet(string $user, array $properties): array {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- } elseif (!$this->userExists($user)) {
- throw new User\Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- $valid = [ // FIXME: add future properties
- "name" => "str",
- ];
- list($setClause, $setTypes, $setValues) = $this->generateSet($properties, $valid);
- if (!$setClause) {
- // if no changes would actually be applied, just return
- return $this->userPropertiesGet($user);
- }
- $this->db->prepare("UPDATE arsse_users set $setClause where id = ?", $setTypes, "str")->run($setValues, $user);
- return $this->userPropertiesGet($user);
- }
-
- public function userRightsGet(string $user): int {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- return (int) $this->db->prepare("SELECT rights from arsse_users where id = ?", "str")->run($user)->getValue();
- }
-
- public function userRightsSet(string $user, int $rights): bool {
- if (!Arsse::$user->authorize($user, __FUNCTION__, $rights)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- } elseif (!$this->userExists($user)) {
- throw new User\Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- $this->db->prepare("UPDATE arsse_users set rights = ? where id = ?", "int", "str")->run($rights, $user);
- 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__)) {
@@ -596,10 +539,7 @@ class Database {
if (!ValueInfo::id($id)) {
throw new Db\ExceptionInput("typeViolation", ["action" => __FUNCTION__, "field" => "feed", 'type' => "int > 0"]);
}
- // disable authorization checks for the list call
- Arsse::$user->authorizationEnabled(false);
$sub = $this->subscriptionList($user, null, true, (int) $id)->getRow();
- Arsse::$user->authorizationEnabled(true);
if (!$sub) {
throw new Db\ExceptionInput("subjectMissing", ["action" => __FUNCTION__, "field" => "feed", 'id' => $id]);
}
diff --git a/lib/REST.php b/lib/REST.php
index 0dcd6ecd..ac527f1d 100644
--- a/lib/REST.php
+++ b/lib/REST.php
@@ -135,7 +135,7 @@ class REST {
$user = $env['REMOTE_USER'];
}
if (strlen($user)) {
- if (Arsse::$user->auth($user, $password)) {
+ if (Arsse::$user->auth((string) $user, (string) $password)) {
$req = $req->withAttribute("authenticated", true);
$req = $req->withAttribute("authenticatedUser", $user);
} else {
diff --git a/lib/REST/NextCloudNews/V1_2.php b/lib/REST/NextCloudNews/V1_2.php
index eb78d969..0ae19792 100644
--- a/lib/REST/NextCloudNews/V1_2.php
+++ b/lib/REST/NextCloudNews/V1_2.php
@@ -365,10 +365,6 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): ResponseInterface {
- // function requires admin rights per spec
- if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
- return new EmptyResponse(403);
- }
// list stale feeds which should be checked for updates
$feeds = Arsse::$db->feedListStale();
$out = [];
@@ -381,10 +377,6 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
// refresh a feed
protected function feedUpdate(array $url, array $data): ResponseInterface {
- // function requires admin rights per spec
- if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
- return new EmptyResponse(403);
- }
try {
Arsse::$db->feedUpdate($data['feedId']);
} catch (ExceptionInput $e) {
@@ -680,19 +672,11 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
}
protected function cleanupBefore(array $url, array $data): ResponseInterface {
- // function requires admin rights per spec
- if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
- return new EmptyResponse(403);
- }
Service::cleanupPre();
return new EmptyResponse(204);
}
protected function cleanupAfter(array $url, array $data): ResponseInterface {
- // function requires admin rights per spec
- if (Arsse::$user->rightsGet(Arsse::$user->id)==User::RIGHTS_NONE) {
- return new EmptyResponse(403);
- }
Service::cleanupPost();
return new EmptyResponse(204);
}
diff --git a/lib/User.php b/lib/User.php
index 2c2ad05a..2e130345 100644
--- a/lib/User.php
+++ b/lib/User.php
@@ -7,11 +7,6 @@ declare(strict_types=1);
namespace JKingWeb\Arsse;
class User {
- const RIGHTS_NONE = 0; // normal user
- const RIGHTS_DOMAIN_MANAGER = 25; // able to act for any normal users on same domain; cannot elevate other users
- const RIGHTS_DOMAIN_ADMIN = 50; // able to act for any users on same domain not above themselves; may elevate users on same domain to domain manager or domain admin
- const RIGHTS_GLOBAL_MANAGER = 75; // able to act for any normal users on any domain; cannot elevate other users
- const RIGHTS_GLOBAL_ADMIN = 100; // is completely unrestricted
public $id = null;
@@ -19,9 +14,6 @@ class User {
* @var User\Driver
*/
protected $u;
- protected $authz = 0;
- protected $authzSupported = 0;
- protected $actor = [];
public static function driverList(): array {
$sep = \DIRECTORY_SEPARATOR;
@@ -41,165 +33,59 @@ class User {
}
public function __toString() {
- if ($this->id===null) {
- $this->credentials();
- }
return (string) $this->id;
}
- // checks whether the logged in user is authorized to act for the affected user (used especially when granting rights)
- public function authorize(string $affectedUser, string $action, int $newRightsLevel = 0): bool {
- // if authorization checks are disabled (either because we're running the installer or the background updater) just return true
- if (!$this->authorizationEnabled()) {
- return true;
- }
- // if we don't have a logged-in user, fetch credentials
- if ($this->id===null) {
- $this->credentials();
- }
- // if the affected user is the actor and the actor is not trying to grant themselves rights, accept the request
- if ($affectedUser==Arsse::$user->id && $action != "userRightsSet") {
- return true;
- }
- // if we're authorizing something other than a user function and the affected user is not the actor, make sure the affected user exists
- $this->authorizationEnabled(false);
- if (Arsse::$user->id != $affectedUser && strpos($action, "user")!==0 && !$this->exists($affectedUser)) {
- throw new User\Exception("doesNotExist", ["action" => $action, "user" => $affectedUser]);
- }
- $this->authorizationEnabled(true);
- // get properties of actor if not already available
- if (!sizeof($this->actor)) {
- $this->actor = $this->propertiesGet(Arsse::$user->id);
- }
- $rights = $this->actor["rights"];
- // if actor is a global admin, accept the request
- if ($rights==User\Driver::RIGHTS_GLOBAL_ADMIN) {
- return true;
- }
- // if actor is a common user, deny the request
- if ($rights==User\Driver::RIGHTS_NONE) {
- return false;
- }
- // if actor is not some other sort of admin, deny the request
- if (!in_array($rights, [User\Driver::RIGHTS_GLOBAL_MANAGER,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN], true)) {
- return false;
- }
- // if actor is a domain admin/manager and domains don't match, deny the request
- if ($this->actor["domain"] && $rights != User\Driver::RIGHTS_GLOBAL_MANAGER) {
- $test = "@".$this->actor["domain"];
- if (substr($affectedUser, -1*strlen($test)) != $test) {
- return false;
- }
- }
- // certain actions shouldn't check affected user's rights
- if (in_array($action, ["userRightsGet","userExists","userList"], true)) {
- return true;
- }
- if ($action=="userRightsSet") {
- // setting rights above your own is not allowed
- if ($newRightsLevel > $rights) {
- return false;
- }
- // setting yourself to rights you already have is harmless and can be allowed
- if ($this->id==$affectedUser && $newRightsLevel==$rights) {
- return true;
- }
- // managers can only set their own rights, and only to normal user
- if (in_array($rights, [User\Driver::RIGHTS_DOMAIN_MANAGER, User\Driver::RIGHTS_GLOBAL_MANAGER])) {
- if ($this->id != $affectedUser || $newRightsLevel != User\Driver::RIGHTS_NONE) {
- return false;
- }
- return true;
- }
- }
- $affectedRights = $this->rightsGet($affectedUser);
- // managers can only act on themselves (checked above) or regular users
- if (in_array($rights, [User\Driver::RIGHTS_GLOBAL_MANAGER,User\Driver::RIGHTS_DOMAIN_MANAGER]) && $affectedRights != User\Driver::RIGHTS_NONE) {
- return false;
- }
- // domain admins canot act above themselves
- if (!in_array($affectedRights, [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN])) {
- return false;
- }
+ // at one time there was a complicated authorization system; it exists vestigially to support a later revival if desired
+ public function authorize(string $affectedUser, string $action): bool {
return true;
}
- public function credentials(): array {
- if ($_SERVER['PHP_AUTH_USER']) {
- $out = ["user" => $_SERVER['PHP_AUTH_USER'], "password" => $_SERVER['PHP_AUTH_PW']];
- } elseif ($_SERVER['REMOTE_USER']) {
- $out = ["user" => $_SERVER['REMOTE_USER'], "password" => ""];
- } else {
- $out = ["user" => "", "password" => ""];
- }
- $this->id = $out["user"];
- return $out;
- }
-
- public function auth(string $user = null, string $password = null): bool {
- if ($user===null) {
- return $this->authHTTP();
- } else {
- $prevUser = $this->id ?? null;
- $this->id = $user;
- $this->actor = [];
- switch ($this->u->driverFunctions("auth")) {
- case User\Driver::FUNC_EXTERNAL:
- if (Arsse::$conf->userPreAuth) {
- $out = true;
- } else {
- $out = $this->u->auth($user, $password);
- }
- if ($out && !Arsse::$db->userExists($user)) {
+ public function auth(string $user, string $password): bool {
+ $prevUser = $this->id ?? null;
+ $this->id = $user;
+ switch ($this->u->driverFunctions("auth")) {
+ case User\Driver::FUNC_EXTERNAL:
+ if (Arsse::$conf->userPreAuth) {
+ $out = true;
+ } else {
+ $out = $this->u->auth($user, $password);
+ }
+ if ($out && !Arsse::$db->userExists($user)) {
+ $this->autoProvision($user, $password);
+ }
+ break;
+ case User\Driver::FUNC_INTERNAL:
+ if (Arsse::$conf->userPreAuth) {
+ if (!Arsse::$db->userExists($user)) {
$this->autoProvision($user, $password);
}
- break;
- case User\Driver::FUNC_INTERNAL:
- if (Arsse::$conf->userPreAuth) {
- if (!Arsse::$db->userExists($user)) {
- $this->autoProvision($user, $password);
- }
- $out = true;
- } else {
- $out = $this->u->auth($user, $password);
- }
- break;
- case User\Driver::FUNCT_NOT_IMPLEMENTED:
- $out = false;
- break;
- }
- if (!$out) {
- $this->id = $prevUser;
- }
- return $out;
+ $out = true;
+ } else {
+ $out = $this->u->auth($user, $password);
+ }
+ break;
+ case User\Driver::FUNCT_NOT_IMPLEMENTED:
+ $out = false;
+ break;
}
- }
-
- public function authHTTP(): bool {
- $cred = $this->credentials();
- if (!$cred["user"]) {
- return false;
+ if (!$out) {
+ $this->id = $prevUser;
}
- return $this->auth($cred["user"], $cred["password"]);
+ return $out;
}
public function driverFunctions(string $function = null) {
return $this->u->driverFunctions($function);
}
- public function list(string $domain = null): array {
+ public function list(): array {
$func = "userList";
switch ($this->u->driverFunctions($func)) {
case User\Driver::FUNC_EXTERNAL:
// we handle authorization checks for external drivers
- if ($domain===null) {
- if (!$this->authorize("@".$domain, $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => $domain]);
- }
- } else {
- if (!$this->authorize("", $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => "all users"]);
- }
+ if (!$this->authorize("", $func)) {
+ throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => ""]);
}
// no break
case User\Driver::FUNC_INTERNAL:
@@ -210,17 +96,6 @@ class User {
}
}
- public function authorizationEnabled(bool $setting = null): bool {
- if (is_null($setting)) {
- return !$this->authz;
- }
- $this->authz += ($setting ? -1 : 1);
- if ($this->authz < 0) {
- $this->authz = 0;
- }
- return !$this->authz;
- }
-
public function exists(string $user): bool {
$func = "userExists";
switch ($this->u->driverFunctions($func)) {
@@ -314,147 +189,8 @@ class User {
}
}
- public function propertiesGet(string $user, bool $withAvatar = false): array {
- // prepare default values
- $domain = null;
- if (strrpos($user, "@")!==false) {
- $domain = substr($user, strrpos($user, "@")+1);
- }
- $init = [
- "id" => $user,
- "name" => $user,
- "rights" => User\Driver::RIGHTS_NONE,
- "domain" => $domain
- ];
- $func = "userPropertiesGet";
- switch ($this->u->driverFunctions($func)) {
- case User\Driver::FUNC_EXTERNAL:
- // we handle authorization checks for external drivers
- if (!$this->authorize($user, $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => $user]);
- }
- $out = array_merge($init, $this->u->userPropertiesGet($user));
- // remove password if it is return (not exhaustive, but...)
- if (array_key_exists('password', $out)) {
- unset($out['password']);
- }
- // if the user does not exist in the internal database, add it
- if (!Arsse::$db->userExists($user)) {
- $this->autoProvision($user, "", $out);
- }
- return $out;
- case User\Driver::FUNC_INTERNAL:
- // internal functions handle their own authorization
- return array_merge($init, $this->u->userPropertiesGet($user));
- case User\Driver::FUNCT_NOT_IMPLEMENTED:
- // we can return generic values if the function is not implemented
- return $init;
- }
- }
-
- public function propertiesSet(string $user, array $properties): array {
- // remove from the array any values which should be set specially
- foreach (['id', 'domain', 'password', 'rights'] as $key) {
- if (array_key_exists($key, $properties)) {
- unset($properties[$key]);
- }
- }
- $func = "userPropertiesSet";
- switch ($this->u->driverFunctions($func)) {
- case User\Driver::FUNC_EXTERNAL:
- // we handle authorization checks for external drivers
- if (!$this->authorize($user, $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => $user]);
- }
- $out = $this->u->userPropertiesSet($user, $properties);
- if (Arsse::$db->userExists($user)) {
- // if the property change was successful and the user exists, set the internal properties to the same values
- Arsse::$db->userPropertiesSet($user, $out);
- } else {
- // if the user does not exists in the internal database, create it
- $this->autoProvision($user, "", $out);
- }
- return $out;
- case User\Driver::FUNC_INTERNAL:
- // internal functions handle their own authorization
- return $this->u->userPropertiesSet($user, $properties);
- case User\Driver::FUNCT_NOT_IMPLEMENTED:
- throw new User\ExceptionNotImplemented("notImplemented", ["action" => $func, "user" => $user]);
- }
- }
-
- public function rightsGet(string $user): int {
- $func = "userRightsGet";
- switch ($this->u->driverFunctions($func)) {
- case User\Driver::FUNC_EXTERNAL:
- // we handle authorization checks for external drivers
- if (!$this->authorize($user, $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => $user]);
- }
- $out = $this->u->userRightsGet($user);
- // if the user does not exist in the internal database, add it
- if (!Arsse::$db->userExists($user)) {
- $this->autoProvision($user, "", null, $out);
- }
- return $out;
- case User\Driver::FUNC_INTERNAL:
- // internal functions handle their own authorization
- return $this->u->userRightsGet($user);
- case User\Driver::FUNCT_NOT_IMPLEMENTED:
- // assume all users are unprivileged
- return User\Driver::RIGHTS_NONE;
- }
- }
-
- public function rightsSet(string $user, int $level): bool {
- $func = "userRightsSet";
- switch ($this->u->driverFunctions($func)) {
- case User\Driver::FUNC_EXTERNAL:
- // we handle authorization checks for external drivers
- if (!$this->authorize($user, $func)) {
- throw new User\ExceptionAuthz("notAuthorized", ["action" => $func, "user" => $user]);
- }
- $out = $this->u->userRightsSet($user, $level);
- // if the user does not exist in the internal database, add it
- if ($out && Arsse::$db->userExists($user)) {
- $authz = $this->authorizationEnabled();
- $this->authorizationEnabled(false);
- Arsse::$db->userRightsSet($user, $level);
- $this->authorizationEnabled($authz);
- } elseif ($out) {
- $this->autoProvision($user, "", null, $level);
- }
- return $out;
- case User\Driver::FUNC_INTERNAL:
- // internal functions handle their own authorization
- return $this->u->userRightsSet($user, $level);
- case User\Driver::FUNCT_NOT_IMPLEMENTED:
- throw new User\ExceptionNotImplemented("notImplemented", ["action" => $func, "user" => $user]);
- }
- }
-
- protected function autoProvision(string $user, string $password = null, array $properties = null, int $rights = 0): string {
- // temporarily disable authorization checks, to avoid potential problems
- $this->authorizationEnabled(false);
- // create the user
+ protected function autoProvision(string $user, string $password = null): string {
$out = Arsse::$db->userAdd($user, $password);
- // set the user rights
- Arsse::$db->userRightsSet($user, $rights);
- // set the user properties...
- if ($properties===null) {
- // if nothing is provided but the driver uses an external function, try to get the current values from the external source
- try {
- if ($this->u->driverFunctions("userPropertiesGet")==User\Driver::FUNC_EXTERNAL) {
- Arsse::$db->userPropertiesSet($user, $this->u->userPropertiesGet($user));
- }
- } catch (\Throwable $e) {
- }
- } else {
- // otherwise if values are provided, use those
- Arsse::$db->userPropertiesSet($user, $properties);
- }
- // re-enable authorization and return
- $this->authorizationEnabled(true);
return $out;
}
}
diff --git a/lib/User/Driver.php b/lib/User/Driver.php
index 86718ee1..7b07dc4e 100644
--- a/lib/User/Driver.php
+++ b/lib/User/Driver.php
@@ -11,12 +11,6 @@ interface Driver {
const FUNC_INTERNAL = 1;
const FUNC_EXTERNAL = 2;
- const RIGHTS_NONE = 0; // normal user
- const RIGHTS_DOMAIN_MANAGER = 25; // able to act for any normal users on same domain; cannot elevate other users
- const RIGHTS_DOMAIN_ADMIN = 50; // able to act for any users on same domain not above themselves; may elevate users on same domain to domain manager or domain admin
- const RIGHTS_GLOBAL_MANAGER = 75; // able to act for any normal users on any domain; cannot elevate other users
- const RIGHTS_GLOBAL_ADMIN = 100; // is completely unrestricted
-
// returns an instance of a class implementing this interface.
public function __construct();
// returns a human-friendly name for the driver (for display in installer, for example)
@@ -32,15 +26,7 @@ interface Driver {
// removes a user
public function userRemove(string $user): bool;
// lists all users
- public function userList(string $domain = null): 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): string;
- // gets user metadata (currently not useful)
- public function userPropertiesGet(string $user): array;
- // sets user metadata (currently not useful)
- public function userPropertiesSet(string $user, array $properties): array;
- // returns a user's access level according to RIGHTS_* constants (or some custom semantics, if using custom implementation of authorize())
- public function userRightsGet(string $user): int;
- // sets a user's access level
- public function userRightsSet(string $user, int $level): bool;
}
diff --git a/lib/User/Internal/Driver.php b/lib/User/Internal/Driver.php
index 3f7a555a..ad3e50d9 100644
--- a/lib/User/Internal/Driver.php
+++ b/lib/User/Internal/Driver.php
@@ -17,10 +17,6 @@ final class Driver implements \JKingWeb\Arsse\User\Driver {
"userAdd" => self::FUNC_INTERNAL,
"userRemove" => self::FUNC_INTERNAL,
"userPasswordSet" => self::FUNC_INTERNAL,
- "userPropertiesGet" => self::FUNC_INTERNAL,
- "userPropertiesSet" => self::FUNC_INTERNAL,
- "userRightsGet" => self::FUNC_INTERNAL,
- "userRightsSet" => self::FUNC_INTERNAL,
];
public static function driverName(): string {
diff --git a/lib/User/Internal/InternalFunctions.php b/lib/User/Internal/InternalFunctions.php
index b88d15ad..e834b3d9 100644
--- a/lib/User/Internal/InternalFunctions.php
+++ b/lib/User/Internal/InternalFunctions.php
@@ -39,27 +39,11 @@ trait InternalFunctions {
return Arsse::$db->userRemove($user);
}
- public function userList(string $domain = null): array {
- return Arsse::$db->userList($domain);
+ public function userList(): array {
+ return Arsse::$db->userList();
}
public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
return Arsse::$db->userPasswordSet($user, $newPassword);
}
-
- public function userPropertiesGet(string $user): array {
- return Arsse::$db->userPropertiesGet($user);
- }
-
- public function userPropertiesSet(string $user, array $properties): array {
- return Arsse::$db->userPropertiesSet($user, $properties);
- }
-
- public function userRightsGet(string $user): int {
- return Arsse::$db->userRightsGet($user);
- }
-
- public function userRightsSet(string $user, int $level): bool {
- return Arsse::$db->userRightsSet($user, $level);
- }
}
diff --git a/locale/en.php b/locale/en.php
index 477f04a3..66ae80df 100644
--- a/locale/en.php
+++ b/locale/en.php
@@ -163,10 +163,7 @@ return [
'Exception.JKingWeb/Arsse/User/Exception.authFailed' => 'Authentication failed',
'Exception.JKingWeb/Arsse/User/ExceptionAuthz.notAuthorized' =>
'{action, select,
- userList {{user, select,
- global {Authenticated user is not authorized to view the global user list}
- other {Authenticated user is not authorized to view the user list for domain {user}}
- }}
+ userList {Authenticated user is not authorized to view the user list}
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',
diff --git a/tests/cases/REST/NextCloudNews/TestV1_2.php b/tests/cases/REST/NextCloudNews/TestV1_2.php
index 2ee0b1b1..4ee6e513 100644
--- a/tests/cases/REST/NextCloudNews/TestV1_2.php
+++ b/tests/cases/REST/NextCloudNews/TestV1_2.php
@@ -314,7 +314,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$server['HTTP_CONTENT_TYPE'] = "application/json";
}
$req = new ServerRequest($server, [], $url, $method, "php://memory");
- if (Arsse::$user->auth()) {
+ if (Arsse::$user->auth("john.doe@example.com", "secret")) {
$req = $req->withAttribute("authenticated", true)->withAttribute("authenticatedUser", "john.doe@example.com");
}
foreach ($headers as $key => $value) {
@@ -344,7 +344,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
// create a mock user manager
Arsse::$user = Phake::mock(User::class);
Phake::when(Arsse::$user)->auth->thenReturn(true);
- Phake::when(Arsse::$user)->rightsGet->thenReturn(100);
Arsse::$user->id = "john.doe@example.com";
// create a mock database interface
Arsse::$db = Phake::mock(Database::class);
@@ -696,10 +695,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
Phake::when(Arsse::$db)->feedListStale->thenReturn($this->v(array_column($out, "id")));
$exp = new Response(['feeds' => $out]);
$this->assertMessage($exp, $this->req("GET", "/feeds/all"));
- // retrieving the list when not an admin fails
- Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
- $exp = new EmptyResponse(403);
- $this->assertMessage($exp, $this->req("GET", "/feeds/all"));
}
public function testUpdateAFeed() {
@@ -721,10 +716,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[2])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[3])));
$this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[4])));
- // updating a feed when not an admin fails
- Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
- $exp = new EmptyResponse(403);
- $this->assertMessage($exp, $this->req("GET", "/feeds/update", json_encode($in[0])));
}
public function testListArticles() {
@@ -929,10 +920,6 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$exp = new EmptyResponse(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
Phake::verify(Arsse::$db)->feedCleanup();
- // performing a cleanup when not an admin fails
- Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
- $exp = new EmptyResponse(403);
- $this->assertMessage($exp, $this->req("GET", "/cleanup/before-update"));
}
public function testCleanUpAfterUpdate() {
@@ -940,9 +927,5 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest {
$exp = new EmptyResponse(204);
$this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
Phake::verify(Arsse::$db)->articleCleanup();
- // performing a cleanup when not an admin fails
- Phake::when(Arsse::$user)->rightsGet->thenReturn(0);
- $exp = new EmptyResponse(403);
- $this->assertMessage($exp, $this->req("GET", "/cleanup/after-update"));
}
}
diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php
index 9aea12e0..e6861c1e 100644
--- a/tests/cases/REST/TinyTinyRSS/TestAPI.php
+++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php
@@ -181,7 +181,6 @@ LONG_STRING;
// create a mock user manager
Arsse::$user = Phake::mock(User::class);
Phake::when(Arsse::$user)->auth->thenReturn(true);
- Phake::when(Arsse::$user)->rightsGet->thenReturn(100);
Arsse::$user->id = "john.doe@example.com";
// create a mock database interface
Arsse::$db = Phake::mock(Database::class);
diff --git a/tests/cases/User/TestAuthorization.php b/tests/cases/User/TestAuthorization.php
deleted file mode 100644
index 6bbb0fb1..00000000
--- a/tests/cases/User/TestAuthorization.php
+++ /dev/null
@@ -1,338 +0,0 @@
- Driver::RIGHTS_NONE,
- 'user@example.org' => Driver::RIGHTS_NONE,
- 'dman@example.com' => Driver::RIGHTS_DOMAIN_MANAGER,
- 'dman@example.org' => Driver::RIGHTS_DOMAIN_MANAGER,
- 'dadm@example.com' => Driver::RIGHTS_DOMAIN_ADMIN,
- 'dadm@example.org' => Driver::RIGHTS_DOMAIN_ADMIN,
- 'gman@example.com' => Driver::RIGHTS_GLOBAL_MANAGER,
- 'gman@example.org' => Driver::RIGHTS_GLOBAL_MANAGER,
- 'gadm@example.com' => Driver::RIGHTS_GLOBAL_ADMIN,
- 'gadm@example.org' => Driver::RIGHTS_GLOBAL_ADMIN,
- // invalid rights levels
- 'bad1@example.com' => Driver::RIGHTS_NONE+1,
- 'bad1@example.org' => Driver::RIGHTS_NONE+1,
- 'bad2@example.com' => Driver::RIGHTS_DOMAIN_MANAGER+1,
- 'bad2@example.org' => Driver::RIGHTS_DOMAIN_MANAGER+1,
- 'bad3@example.com' => Driver::RIGHTS_DOMAIN_ADMIN+1,
- 'bad3@example.org' => Driver::RIGHTS_DOMAIN_ADMIN+1,
- 'bad4@example.com' => Driver::RIGHTS_GLOBAL_MANAGER+1,
- 'bad4@example.org' => Driver::RIGHTS_GLOBAL_MANAGER+1,
- 'bad5@example.com' => Driver::RIGHTS_GLOBAL_ADMIN+1,
- 'bad5@example.org' => Driver::RIGHTS_GLOBAL_ADMIN+1,
-
- ];
- const LEVELS = [
- Driver::RIGHTS_NONE,
- Driver::RIGHTS_DOMAIN_MANAGER,
- Driver::RIGHTS_DOMAIN_ADMIN,
- Driver::RIGHTS_GLOBAL_MANAGER,
- Driver::RIGHTS_GLOBAL_ADMIN,
- ];
- const DOMAINS = [
- '@example.com',
- '@example.org',
- "",
- ];
-
- protected $data;
-
- public function setUp(string $drv = \JkingWeb\Arsse\Test\User\DriverInternalMock::class, string $db = null) {
- $this->clearData();
- $conf = new Conf();
- $conf->userDriver = $drv;
- $conf->userPreAuth = false;
- Arsse::$conf = $conf;
- if ($db !== null) {
- Arsse::$db = new $db();
- }
- Arsse::$user = Phake::partialMock(User::class);
- Phake::when(Arsse::$user)->authorize->thenReturn(true);
- foreach (self::USERS as $user => $level) {
- Arsse::$user->add($user, "");
- Arsse::$user->rightsSet($user, $level);
- }
- Phake::reset(Arsse::$user);
- }
-
- public function tearDown() {
- $this->clearData();
- }
-
- public function testToggleLogic() {
- $this->assertTrue(Arsse::$user->authorizationEnabled());
- $this->assertTrue(Arsse::$user->authorizationEnabled(true));
- $this->assertFalse(Arsse::$user->authorizationEnabled(false));
- $this->assertFalse(Arsse::$user->authorizationEnabled(false));
- $this->assertFalse(Arsse::$user->authorizationEnabled(true));
- $this->assertTrue(Arsse::$user->authorizationEnabled(true));
- }
-
- public function testSelfActionLogic() {
- foreach (array_keys(self::USERS) as $user) {
- Arsse::$user->auth($user, "");
- // users should be able to do basic actions for themselves
- $this->assertTrue(Arsse::$user->authorize($user, "userExists"), "User $user could not act for themselves.");
- $this->assertTrue(Arsse::$user->authorize($user, "userRemove"), "User $user could not act for themselves.");
- }
- }
-
- public function testRegularUserLogic() {
- foreach (self::USERS as $actor => $rights) {
- if ($rights != Driver::RIGHTS_NONE) {
- continue;
- }
- Arsse::$user->auth($actor, "");
- foreach (array_keys(self::USERS) as $affected) {
- // regular users should only be able to act for themselves
- if ($actor==$affected) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
- $this->assertFalse(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // they should never be able to set rights
- foreach (self::LEVELS as $level) {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
- }
- }
- // they should not be able to list users
- foreach (self::DOMAINS as $domain) {
- $this->assertFalse(Arsse::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
- }
- }
- }
-
- public function testDomainManagerLogic() {
- foreach (self::USERS as $actor => $actorRights) {
- if ($actorRights != Driver::RIGHTS_DOMAIN_MANAGER) {
- continue;
- }
- $actorDomain = substr($actor, strrpos($actor, "@")+1);
- Arsse::$user->auth($actor, "");
- foreach (self::USERS as $affected => $affectedRights) {
- $affectedDomain = substr($affected, strrpos($affected, "@")+1);
- // domain managers should be able to check any user on the same domain
- if ($actorDomain==$affectedDomain) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // they should only be able to act for regular users on the same domain
- if ($actor==$affected || ($actorDomain==$affectedDomain && $affectedRights==User\Driver::RIGHTS_NONE)) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // and they should only be able to set their own rights to regular user
- foreach (self::LEVELS as $level) {
- if ($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, Driver::RIGHTS_DOMAIN_MANAGER])) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
- }
- }
- }
- // they should also be able to list all users on their own domain
- foreach (self::DOMAINS as $domain) {
- if ($domain=="@".$actorDomain) {
- $this->assertTrue(Arsse::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
- }
- }
- }
- }
-
- public function testDomainAdministratorLogic() {
- foreach (self::USERS as $actor => $actorRights) {
- if ($actorRights != Driver::RIGHTS_DOMAIN_ADMIN) {
- continue;
- }
- $actorDomain = substr($actor, strrpos($actor, "@")+1);
- Arsse::$user->auth($actor, "");
- $allowed = [User\Driver::RIGHTS_NONE,User\Driver::RIGHTS_DOMAIN_MANAGER,User\Driver::RIGHTS_DOMAIN_ADMIN];
- foreach (self::USERS as $affected => $affectedRights) {
- $affectedDomain = substr($affected, strrpos($affected, "@")+1);
- // domain admins should be able to check any user on the same domain
- if ($actorDomain==$affectedDomain) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // they should be able to act for any user on the same domain who is not a global manager or admin
- if ($actorDomain==$affectedDomain && in_array($affectedRights, $allowed)) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // they should be able to set rights for any user on their domain who is not a global manager or admin, up to domain admin level
- foreach (self::LEVELS as $level) {
- if ($actorDomain==$affectedDomain && in_array($affectedRights, $allowed) && in_array($level, $allowed)) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
- }
- }
- }
- // they should also be able to list all users on their own domain
- foreach (self::DOMAINS as $domain) {
- if ($domain=="@".$actorDomain) {
- $this->assertTrue(Arsse::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
- }
- }
- }
- }
-
- public function testGlobalManagerLogic() {
- foreach (self::USERS as $actor => $actorRights) {
- if ($actorRights != Driver::RIGHTS_GLOBAL_MANAGER) {
- continue;
- }
- $actorDomain = substr($actor, strrpos($actor, "@")+1);
- Arsse::$user->auth($actor, "");
- foreach (self::USERS as $affected => $affectedRights) {
- $affectedDomain = substr($affected, strrpos($affected, "@")+1);
- // global managers should be able to check any user
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- // they should only be able to act for regular users
- if ($actor==$affected || $affectedRights==User\Driver::RIGHTS_NONE) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // and they should only be able to set their own rights to regular user
- foreach (self::LEVELS as $level) {
- if ($actor==$affected && in_array($level, [User\Driver::RIGHTS_NONE, Driver::RIGHTS_GLOBAL_MANAGER])) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
- }
- }
- }
- // they should also be able to list all users
- foreach (self::DOMAINS as $domain) {
- $this->assertTrue(Arsse::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
- }
- }
- }
-
- public function testGlobalAdministratorLogic() {
- foreach (self::USERS as $actor => $actorRights) {
- if ($actorRights != Driver::RIGHTS_GLOBAL_ADMIN) {
- continue;
- }
- Arsse::$user->auth($actor, "");
- // global admins can do anything
- foreach (self::USERS as $affected => $affectedRights) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- foreach (self::LEVELS as $level) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted properly for $affected settings rights level $level, but the action was denied.");
- }
- }
- foreach (self::DOMAINS as $domain) {
- $this->assertTrue(Arsse::$user->authorize($domain, "userList"), "User $actor properly checked user list for domain '$domain', but the action was denied.");
- }
- }
- }
-
- public function testInvalidLevelLogic() {
- foreach (self::USERS as $actor => $rights) {
- if (in_array($rights, self::LEVELS)) {
- continue;
- }
- Arsse::$user->auth($actor, "");
- foreach (array_keys(self::USERS) as $affected) {
- // users with unknown/invalid rights should be treated just like regular users and only be able to act for themselves
- if ($actor==$affected) {
- $this->assertTrue(Arsse::$user->authorize($affected, "userExists"), "User $actor acted properly for $affected, but the action was denied.");
- $this->assertTrue(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted properly for $affected, but the action was denied.");
- } else {
- $this->assertFalse(Arsse::$user->authorize($affected, "userExists"), "User $actor acted improperly for $affected, but the action was allowed.");
- $this->assertFalse(Arsse::$user->authorize($affected, "userRemove"), "User $actor acted improperly for $affected, but the action was allowed.");
- }
- // they should never be able to set rights
- foreach (self::LEVELS as $level) {
- $this->assertFalse(Arsse::$user->authorize($affected, "userRightsSet", $level), "User $actor acted improperly for $affected settings rights level $level, but the action was allowed.");
- }
- }
- // they should not be able to list users
- foreach (self::DOMAINS as $domain) {
- $this->assertFalse(Arsse::$user->authorize($domain, "userList"), "User $actor improperly checked user list for domain '$domain', but the action was allowed.");
- }
- }
- }
-
- public function testInternalExceptionLogic() {
- $tests = [
- // methods of User class to test, with parameters besides affected user
- 'exists' => [],
- 'remove' => [],
- 'add' => [''],
- 'passwordSet' => [''],
- 'propertiesGet' => [],
- 'propertiesSet' => [[]],
- 'rightsGet' => [],
- 'rightsSet' => [User\Driver::RIGHTS_GLOBAL_ADMIN],
- 'list' => [],
- ];
- // try first with a global admin (there should be no exception)
- Arsse::$user->auth("gadm@example.com", "");
- $this->assertCount(0, $this->checkExceptions("user@example.org", $tests));
- // next try with a regular user acting on another user (everything should fail)
- Arsse::$user->auth("user@example.com", "");
- $this->assertCount(sizeof($tests), $this->checkExceptions("user@example.org", $tests));
- }
-
- public function testExternalExceptionLogic() {
- // set up the test for an external driver
- $this->setUp(\JKingWeb\Arsse\Test\User\DriverExternalMock::class, \JKingWeb\Arsse\Test\User\Database::class);
- // run the previous test with the external driver set up
- $this->testInternalExceptionLogic();
- }
-
- // meat of testInternalExceptionLogic and testExternalExceptionLogic
- // calls each requested function with supplied arguments, catches authorization exceptions, and returns an array of caught failed calls
- protected function checkExceptions(string $user, $tests): array {
- $err = [];
- foreach ($tests as $func => $args) {
- // list method does not take an affected user, so do not unshift for that one
- if ($func != "list") {
- array_unshift($args, $user);
- }
- try {
- call_user_func_array(array(Arsse::$user, $func), $args);
- } catch (\JKingWeb\Arsse\User\ExceptionAuthz $e) {
- $err[] = $func;
- }
- }
- return $err;
- }
-
- public function testMissingUserLogic() {
- Arsse::$user->auth("gadm@example.com", "");
- $this->assertTrue(Arsse::$user->authorize("user@example.com", "someFunction"));
- $this->assertException("doesNotExist", "User");
- Arsse::$user->authorize("this_user_does_not_exist@example.org", "someFunction");
- }
-}
diff --git a/tests/cases/User/TestMockExternal.php b/tests/cases/User/TestMockExternal.php
deleted file mode 100644
index 928edc7c..00000000
--- a/tests/cases/User/TestMockExternal.php
+++ /dev/null
@@ -1,17 +0,0 @@
- 'int',
],
'rows' => [
- ["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', "Hard Lip Herbert", UserDriver::RIGHTS_GLOBAL_ADMIN], // password is hash of "secret"
- ["jane.doe@example.com", "", "Jane Doe", UserDriver::RIGHTS_NONE],
- ["john.doe@example.com", "", "John Doe", UserDriver::RIGHTS_NONE],
+ ["admin@example.net", '$2y$10$PbcG2ZR3Z8TuPzM7aHTF8.v61dtCjzjK78gdZJcp4UePE8T9jEgBW', "Hard Lip Herbert", 100], // password is hash of "secret"
+ ["jane.doe@example.com", "", "Jane Doe", 0],
+ ["john.doe@example.com", "", "John Doe", 0],
],
],
];
@@ -63,7 +63,7 @@ trait SeriesUser {
$this->assertSame("", Arsse::$db->userAdd("john.doe@example.org", ""));
Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userAdd");
$state = $this->primeExpectations($this->data, ['arsse_users' => ['id','name','rights']]);
- $state['arsse_users']['rows'][] = ["john.doe@example.org", null, UserDriver::RIGHTS_NONE];
+ $state['arsse_users']['rows'][] = ["john.doe@example.org", null, 0];
$this->compareExpectations($state);
}
@@ -125,24 +125,12 @@ trait SeriesUser {
Phake::verify(Arsse::$user)->authorize("", "userList");
}
- public function testListUsersOnADomain() {
- $users = ["jane.doe@example.com", "john.doe@example.com"];
- $this->assertSame($users, Arsse::$db->userList("example.com"));
- Phake::verify(Arsse::$user)->authorize("@example.com", "userList");
- }
-
public function testListAllUsersWithoutAuthority() {
Phake::when(Arsse::$user)->authorize->thenReturn(false);
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
Arsse::$db->userList();
}
- public function testListUsersOnADomainWithoutAuthority() {
- Phake::when(Arsse::$user)->authorize->thenReturn(false);
- $this->assertException("notAuthorized", "User", "ExceptionAuthz");
- Arsse::$db->userList("example.com");
- }
-
/**
* @depends testGetAPassword
*/
@@ -172,105 +160,4 @@ trait SeriesUser {
$this->assertException("notAuthorized", "User", "ExceptionAuthz");
Arsse::$db->userPasswordSet("john.doe@example.com", "secret");
}
-
- public function testGetUserProperties() {
- $exp = [
- 'name' => 'Hard Lip Herbert',
- 'rights' => UserDriver::RIGHTS_GLOBAL_ADMIN,
- ];
- $props = Arsse::$db->userPropertiesGet("admin@example.net");
- Phake::verify(Arsse::$user)->authorize("admin@example.net", "userPropertiesGet");
- $this->assertArraySubset($exp, $props);
- $this->assertArrayNotHasKey("password", $props);
- }
-
- public function testGetThePropertiesOfAMissingUser() {
- $this->assertException("doesNotExist", "User");
- Arsse::$db->userPropertiesGet("john.doe@example.org");
- }
-
- public function testGetUserPropertiesWithoutAuthority() {
- Phake::when(Arsse::$user)->authorize->thenReturn(false);
- $this->assertException("notAuthorized", "User", "ExceptionAuthz");
- Arsse::$db->userPropertiesGet("john.doe@example.com");
- }
-
- public function testSetUserProperties() {
- $try = [
- 'name' => 'James Kirk', // only this should actually change
- 'password' => '000destruct0',
- 'rights' => UserDriver::RIGHTS_NONE,
- 'lifeform' => 'tribble',
- ];
- $exp = [
- 'name' => 'James Kirk',
- 'rights' => UserDriver::RIGHTS_GLOBAL_ADMIN,
- ];
- $props = Arsse::$db->userPropertiesSet("admin@example.net", $try);
- Phake::verify(Arsse::$user)->authorize("admin@example.net", "userPropertiesSet");
- $this->assertArraySubset($exp, $props);
- $this->assertArrayNotHasKey("password", $props);
- $state = $this->primeExpectations($this->data, ['arsse_users' => ['id','password','name','rights']]);
- $state['arsse_users']['rows'][0][2] = "James Kirk";
- $this->compareExpectations($state);
- // making now changes should make no changes :)
- Arsse::$db->userPropertiesSet("admin@example.net", ['lifeform' => "tribble"]);
- $this->compareExpectations($state);
- }
-
- public function testSetThePropertiesOfAMissingUser() {
- $try = ['name' => 'John Doe'];
- $this->assertException("doesNotExist", "User");
- Arsse::$db->userPropertiesSet("john.doe@example.org", $try);
- }
-
- public function testSetUserPropertiesWithoutAuthority() {
- $try = ['name' => 'John Doe'];
- Phake::when(Arsse::$user)->authorize->thenReturn(false);
- $this->assertException("notAuthorized", "User", "ExceptionAuthz");
- Arsse::$db->userPropertiesSet("john.doe@example.com", $try);
- }
-
- public function testGetUserRights() {
- $user1 = "john.doe@example.com";
- $user2 = "admin@example.net";
- $this->assertSame(UserDriver::RIGHTS_NONE, Arsse::$db->userRightsGet($user1));
- $this->assertSame(UserDriver::RIGHTS_GLOBAL_ADMIN, Arsse::$db->userRightsGet($user2));
- Phake::verify(Arsse::$user)->authorize($user1, "userRightsGet");
- Phake::verify(Arsse::$user)->authorize($user2, "userRightsGet");
- }
-
- public function testGetTheRightsOfAMissingUser() {
- $this->assertSame(UserDriver::RIGHTS_NONE, Arsse::$db->userRightsGet("john.doe@example.org"));
- Phake::verify(Arsse::$user)->authorize("john.doe@example.org", "userRightsGet");
- }
-
- public function testGetUserRightsWithoutAuthority() {
- Phake::when(Arsse::$user)->authorize->thenReturn(false);
- $this->assertException("notAuthorized", "User", "ExceptionAuthz");
- Arsse::$db->userRightsGet("john.doe@example.com");
- }
-
- public function testSetUserRights() {
- $user = "john.doe@example.com";
- $rights = UserDriver::RIGHTS_GLOBAL_ADMIN;
- $this->assertTrue(Arsse::$db->userRightsSet($user, $rights));
- Phake::verify(Arsse::$user)->authorize($user, "userRightsSet", $rights);
- $state = $this->primeExpectations($this->data, ['arsse_users' => ['id','rights']]);
- $state['arsse_users']['rows'][2][1] = $rights;
- $this->compareExpectations($state);
- }
-
- public function testSetTheRightsOfAMissingUser() {
- $rights = UserDriver::RIGHTS_GLOBAL_ADMIN;
- $this->assertException("doesNotExist", "User");
- Arsse::$db->userRightsSet("john.doe@example.org", $rights);
- }
-
- public function testSetUserRightsWithoutAuthority() {
- $rights = UserDriver::RIGHTS_GLOBAL_ADMIN;
- Phake::when(Arsse::$user)->authorize->thenReturn(false);
- $this->assertException("notAuthorized", "User", "ExceptionAuthz");
- Arsse::$db->userRightsSet("john.doe@example.com", $rights);
- }
}
diff --git a/tests/lib/User/CommonTests.php b/tests/lib/User/CommonTests.php
deleted file mode 100644
index 81d5d48b..00000000
--- a/tests/lib/User/CommonTests.php
+++ /dev/null
@@ -1,154 +0,0 @@
-clearData();
- $conf = new Conf();
- $conf->userDriver = $this->drv;
- $conf->userPreAuth = false;
- Arsse::$conf = $conf;
- Arsse::$db = new Database();
- Arsse::$user = Phake::partialMock(User::class);
- Phake::when(Arsse::$user)->authorize->thenReturn(true);
- $_SERVER['PHP_AUTH_USER'] = self::USER1;
- $_SERVER['PHP_AUTH_PW'] = "secret";
- // call the additional setup method if it exists
- if (method_exists($this, "setUpSeries")) {
- $this->setUpSeries();
- }
- }
-
- public function tearDown() {
- $this->clearData();
- // call the additional teardiwn method if it exists
- if (method_exists($this, "tearDownSeries")) {
- $this->tearDownSeries();
- }
- }
-
- public function testListUsers() {
- $this->assertCount(0, Arsse::$user->list());
- }
-
- public function testCheckIfAUserDoesNotExist() {
- $this->assertFalse(Arsse::$user->exists(self::USER1));
- }
-
- public function testAddAUser() {
- Arsse::$user->add(self::USER1, "");
- $this->assertCount(1, Arsse::$user->list());
- }
-
- public function testCheckIfAUserDoesExist() {
- Arsse::$user->add(self::USER1, "");
- $this->assertTrue(Arsse::$user->exists(self::USER1));
- }
-
- public function testAddADuplicateUser() {
- Arsse::$user->add(self::USER1, "");
- $this->assertException("alreadyExists", "User");
- Arsse::$user->add(self::USER1, "");
- }
-
- public function testAddMultipleUsers() {
- Arsse::$user->add(self::USER1, "");
- Arsse::$user->add(self::USER2, "");
- $this->assertCount(2, Arsse::$user->list());
- }
-
- public function testRemoveAUser() {
- Arsse::$user->add(self::USER1, "");
- $this->assertCount(1, Arsse::$user->list());
- Arsse::$user->remove(self::USER1);
- $this->assertCount(0, Arsse::$user->list());
- }
-
- public function testRemoveAMissingUser() {
- $this->assertException("doesNotExist", "User");
- Arsse::$user->remove(self::USER1);
- }
-
- /** @group slow */
- public function testAuthenticateAUser() {
- $_SERVER['PHP_AUTH_USER'] = self::USER1;
- $_SERVER['PHP_AUTH_PW'] = "secret";
- Arsse::$user->add(self::USER1, "secret");
- Arsse::$user->add(self::USER2, "");
- $this->assertTrue(Arsse::$user->auth());
- $this->assertTrue(Arsse::$user->auth(self::USER1, "secret"));
- $this->assertFalse(Arsse::$user->auth(self::USER1, "superman"));
- $this->assertTrue(Arsse::$user->auth(self::USER2, ""));
- }
-
- /** @group slow */
- public function testChangeAPassword() {
- Arsse::$user->add(self::USER1, "secret");
- $this->assertEquals("superman", Arsse::$user->passwordSet(self::USER1, "superman"));
- $this->assertTrue(Arsse::$user->auth(self::USER1, "superman"));
- $this->assertFalse(Arsse::$user->auth(self::USER1, "secret"));
- $this->assertEquals("", Arsse::$user->passwordSet(self::USER1, ""));
- $this->assertTrue(Arsse::$user->auth(self::USER1, ""));
- $this->assertEquals(Arsse::$conf->userTempPasswordLength, strlen(Arsse::$user->passwordSet(self::USER1)));
- }
-
- public function testChangeAPasswordForAMissingUser() {
- $this->assertException("doesNotExist", "User");
- Arsse::$user->passwordSet(self::USER1, "superman");
- }
-
- public function testGetThePropertiesOfAUser() {
- Arsse::$user->add(self::USER1, "secret");
- $p = Arsse::$user->propertiesGet(self::USER1);
- $this->assertArrayHasKey('id', $p);
- $this->assertArrayHasKey('name', $p);
- $this->assertArrayHasKey('domain', $p);
- $this->assertArrayHasKey('rights', $p);
- $this->assertArrayNotHasKey('password', $p);
- $this->assertEquals(self::USER1, $p['name']);
- }
-
- public function testSetThePropertiesOfAUser() {
- $pSet = [
- 'name' => 'John Doe',
- 'id' => 'invalid',
- 'domain' => 'localhost',
- 'rights' => Driver::RIGHTS_GLOBAL_ADMIN,
- 'password' => 'superman',
- ];
- $pGet = [
- 'name' => 'John Doe',
- 'id' => self::USER1,
- 'domain' => 'example.com',
- 'rights' => Driver::RIGHTS_NONE,
- ];
- Arsse::$user->add(self::USER1, "secret");
- Arsse::$user->propertiesSet(self::USER1, $pSet);
- $p = Arsse::$user->propertiesGet(self::USER1);
- $this->assertArraySubset($pGet, $p);
- $this->assertArrayNotHasKey('password', $p);
- $this->assertFalse(Arsse::$user->auth(self::USER1, "superman"));
- }
-
- public function testGetTheRightsOfAUser() {
- Arsse::$user->add(self::USER1, "");
- $this->assertEquals(Driver::RIGHTS_NONE, Arsse::$user->rightsGet(self::USER1));
- }
-
- public function testSetTheRightsOfAUser() {
- Arsse::$user->add(self::USER1, "");
- Arsse::$user->rightsSet(self::USER1, Driver::RIGHTS_GLOBAL_ADMIN);
- $this->assertEquals(Driver::RIGHTS_GLOBAL_ADMIN, Arsse::$user->rightsGet(self::USER1));
- }
-}
diff --git a/tests/lib/User/Database.php b/tests/lib/User/Database.php
deleted file mode 100644
index 9255d89e..00000000
--- a/tests/lib/User/Database.php
+++ /dev/null
@@ -1,133 +0,0 @@
-authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userExists($user);
- }
-
- public function userAdd(string $user, string $password = null): string {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if ($this->userExists($user)) {
- throw new Exception("alreadyExists", ["action" => __FUNCTION__, "user" => $user]);
- }
- if ($password===null) {
- $password = (new PassGen)->length(Arsse::$conf->userTempPasswordLength)->get();
- }
- return parent::userAdd($user, $password);
- }
-
- public function userRemove(string $user): bool {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRemove($user);
- }
-
- public function userList(string $domain = null): array {
- if ($domain===null) {
- if (!Arsse::$user->authorize("", __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => "global"]);
- }
- return parent::userList();
- } else {
- $suffix = '@'.$domain;
- if (!Arsse::$user->authorize($suffix, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $domain]);
- }
- return parent::userList($domain);
- }
- }
-
- public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- if ($newPassword===null) {
- $newPassword = (new PassGen)->length(Arsse::$conf->userTempPasswordLength)->get();
- }
- return parent::userPasswordSet($user, $newPassword);
- }
-
- public function userPropertiesGet(string $user): array {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- $out = parent::userPropertiesGet($user);
- unset($out['password']);
- return $out;
- }
-
- public function userPropertiesSet(string $user, array $properties): array {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- parent::userPropertiesSet($user, $properties);
- return $this->userPropertiesGet($user);
- }
-
- public function userRightsGet(string $user): int {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRightsGet($user);
- }
-
- public function userRightsSet(string $user, int $level): bool {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRightsSet($user, $level);
- }
-
- // specific to mock database
-
- public function userPasswordGet(string $user): string {
- if (!Arsse::$user->authorize($user, __FUNCTION__)) {
- throw new ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
- }
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return $this->db[$user]['password'];
- }
-}
diff --git a/tests/lib/User/DriverExternalMock.php b/tests/lib/User/DriverExternalMock.php
deleted file mode 100644
index f91d20fd..00000000
--- a/tests/lib/User/DriverExternalMock.php
+++ /dev/null
@@ -1,127 +0,0 @@
- Driver::FUNC_EXTERNAL,
- "userList" => Driver::FUNC_EXTERNAL,
- "userExists" => Driver::FUNC_EXTERNAL,
- "userAdd" => Driver::FUNC_EXTERNAL,
- "userRemove" => Driver::FUNC_EXTERNAL,
- "userPasswordSet" => Driver::FUNC_EXTERNAL,
- "userPropertiesGet" => Driver::FUNC_EXTERNAL,
- "userPropertiesSet" => Driver::FUNC_EXTERNAL,
- "userRightsGet" => Driver::FUNC_EXTERNAL,
- "userRightsSet" => Driver::FUNC_EXTERNAL,
- ];
-
- public static function driverName(): string {
- return "Mock External Driver";
- }
-
- public function driverFunctions(string $function = null) {
- if ($function===null) {
- return $this->functions;
- }
- if (array_key_exists($function, $this->functions)) {
- return $this->functions[$function];
- } else {
- return Driver::FUNC_NOT_IMPLEMENTED;
- }
- }
-
- public function __construct() {
- }
-
- public function auth(string $user, string $password): bool {
- if (!$this->userExists($user)) {
- return false;
- }
- if ($password==="" && $this->db[$user]['password']==="") {
- return true;
- }
- if (password_verify($password, $this->db[$user]['password'])) {
- return true;
- }
- return false;
- }
-
- public function userExists(string $user): bool {
- return parent::userExists($user);
- }
-
- public function userAdd(string $user, string $password = null): string {
- if ($this->userExists($user)) {
- throw new Exception("alreadyExists", ["action" => __FUNCTION__, "user" => $user]);
- }
- if ($password===null) {
- $password = (new PassGen)->length(Arsse::$conf->userTempPasswordLength)->get();
- }
- return parent::userAdd($user, $password);
- }
-
- public function userRemove(string $user): bool {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRemove($user);
- }
-
- public function userList(string $domain = null): array {
- if ($domain===null) {
- return parent::userList();
- } else {
- return parent::userList($domain);
- }
- }
-
- public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- if ($newPassword===null) {
- $newPassword = (new PassGen)->length(Arsse::$conf->userTempPasswordLength)->get();
- }
- return parent::userPasswordSet($user, $newPassword);
- }
-
- public function userPropertiesGet(string $user): array {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userPropertiesGet($user);
- }
-
- public function userPropertiesSet(string $user, array $properties): array {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- parent::userPropertiesSet($user, $properties);
- return $this->userPropertiesGet($user);
- }
-
- public function userRightsGet(string $user): int {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRightsGet($user);
- }
-
- public function userRightsSet(string $user, int $level): bool {
- if (!$this->userExists($user)) {
- throw new Exception("doesNotExist", ["action" => __FUNCTION__, "user" => $user]);
- }
- return parent::userRightsSet($user, $level);
- }
-}
diff --git a/tests/lib/User/DriverInternalMock.php b/tests/lib/User/DriverInternalMock.php
deleted file mode 100644
index c8e64ddb..00000000
--- a/tests/lib/User/DriverInternalMock.php
+++ /dev/null
@@ -1,56 +0,0 @@
- Driver::FUNC_INTERNAL,
- "userList" => Driver::FUNC_INTERNAL,
- "userExists" => Driver::FUNC_INTERNAL,
- "userAdd" => Driver::FUNC_INTERNAL,
- "userRemove" => Driver::FUNC_INTERNAL,
- "userPasswordSet" => Driver::FUNC_INTERNAL,
- "userPropertiesGet" => Driver::FUNC_INTERNAL,
- "userPropertiesSet" => Driver::FUNC_INTERNAL,
- "userRightsGet" => Driver::FUNC_INTERNAL,
- "userRightsSet" => Driver::FUNC_INTERNAL,
- ];
-
- public static function driverName(): string {
- return "Mock Internal Driver";
- }
-
- public function driverFunctions(string $function = null) {
- if ($function===null) {
- return $this->functions;
- }
- if (array_key_exists($function, $this->functions)) {
- return $this->functions[$function];
- } else {
- return Driver::FUNC_NOT_IMPLEMENTED;
- }
- }
-
- public function __construct() {
- }
-
- public function auth(string $user, string $password): bool {
- if (!$this->userExists($user)) {
- return false;
- }
- if ($password==="" && $this->db[$user]['password']==="") {
- return true;
- }
- if (password_verify($password, $this->db[$user]['password'])) {
- return true;
- }
- return false;
- }
-}
diff --git a/tests/lib/User/DriverSkeleton.php b/tests/lib/User/DriverSkeleton.php
deleted file mode 100644
index e9c234bd..00000000
--- a/tests/lib/User/DriverSkeleton.php
+++ /dev/null
@@ -1,72 +0,0 @@
-db);
- }
-
- public function userAdd(string $user, string $password = null): string {
- $u = [
- 'password' => $password ? password_hash($password, \PASSWORD_DEFAULT) : "",
- 'rights' => Driver::RIGHTS_NONE,
- ];
- $this->db[$user] = $u;
- return $password;
- }
-
- public function userRemove(string $user): bool {
- unset($this->db[$user]);
- return true;
- }
-
- public function userList(string $domain = null): array {
- $list = array_keys($this->db);
- if ($domain===null) {
- return $list;
- } else {
- $suffix = '@'.$domain;
- $len = -1 * strlen($suffix);
- return array_filter($list, function ($user) use ($suffix, $len) {
- return substr_compare($user, $suffix, $len);
- });
- }
- }
-
- public function userPasswordSet(string $user, string $newPassword = null, string $oldPassword = null): string {
- $this->db[$user]['password'] = password_hash($newPassword, \PASSWORD_DEFAULT);
- return $newPassword;
- }
-
- public function userPropertiesGet(string $user): array {
- $out = $this->db[$user];
- return $out;
- }
-
- public function userPropertiesSet(string $user, array $properties): array {
- $this->db[$user] = array_merge($this->db[$user], $properties);
- return $this->userPropertiesGet($user);
- }
-
- public function userRightsGet(string $user): int {
- return $this->db[$user]['rights'];
- }
-
- public function userRightsSet(string $user, int $level): bool {
- $this->db[$user]['rights'] = $level;
- return true;
- }
-}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index b58b0cd0..2a653ec2 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -34,10 +34,8 @@
cases/Misc/TestContext.php
- cases/User/TestMockInternal.php
- cases/User/TestMockExternal.php
+ cases/User/TestUser.php
cases/User/TestInternal.php
- cases/User/TestAuthorization.php
cases/Feed/TestFetching.php
@@ -100,4 +98,4 @@
cases/Service/TestService.php
-
\ No newline at end of file
+