From 9ed4bb6f5b0f3f4a052b964d8aa3301b3baffa32 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Thu, 3 Nov 2016 22:54:27 -0400 Subject: [PATCH] Added authorization checks throughout --- locale/en.php | 1 + sql/SQLite3/0.sql | 6 +- vendor/JKingWeb/NewsSync/Database.php | 84 +++++++++---- vendor/JKingWeb/NewsSync/Db/Common.php | 2 +- vendor/JKingWeb/NewsSync/Exception.php | 1 + vendor/JKingWeb/NewsSync/User.php | 117 +++++++++++++++--- vendor/JKingWeb/NewsSync/User/Driver.php | 9 ++ .../JKingWeb/NewsSync/User/DriverInternal.php | 3 + .../JKingWeb/NewsSync/User/ExceptionAuthz.php | 6 + .../NewsSync/User/InternalFunctions.php | 55 +++++--- 10 files changed, 219 insertions(+), 65 deletions(-) create mode 100644 vendor/JKingWeb/NewsSync/User/ExceptionAuthz.php diff --git a/locale/en.php b/locale/en.php index 4897f5e..16d7481 100644 --- a/locale/en.php +++ b/locale/en.php @@ -44,4 +44,5 @@ return [ 'Exception.JKingWeb/NewsSync/User/Exception.doesNotExist' => 'Could not perform action "{action}" because the user {user} does not exist', 'Exception.JKingWeb/NewsSync/User/Exception.authMissing' => 'Please log in to proceed', 'Exception.JKingWeb/NewsSync/User/Exception.authFailed' => 'Authentication failed', + 'Exception.JKingWeb/NewsSync/User/ExceptionAuthz.notAuthorized' => 'Authenticated user is not authorized to perform the action "{action}" on behalf of {user}', ]; \ No newline at end of file diff --git a/sql/SQLite3/0.sql b/sql/SQLite3/0.sql index 0d8cbb2..2375662 100644 --- a/sql/SQLite3/0.sql +++ b/sql/SQLite3/0.sql @@ -6,7 +6,7 @@ create table newssync_feeds( favicon TEXT, -- URL of favicon source TEXT, -- URL of site to which the feed belongs updated datetime, -- time at which the feed was last fetched - modified datetime not null default CURRENT_TIMESTAMP, -- + modified datetime, -- time at which the feed last actually changed err_count integer not null default 0, -- count of successive times update resulted in error since last successful update err_msg TEXT, -- last error message username TEXT not null default '', -- HTTP authentication username @@ -62,9 +62,7 @@ create table newssync_users( avatar_url TEXT, -- external URL to avatar avatar_type TEXT, -- internal avatar image's MIME content type avatar_data BLOB, -- internal avatar image's binary data - admin TEXT check( - admin in('global', 'domain', null) - ) -- whether the user is an administrator + rights integer not null default 0 -- any administrative rights the user may have ); -- TT-RSS categories and ownCloud folders diff --git a/vendor/JKingWeb/NewsSync/Database.php b/vendor/JKingWeb/NewsSync/Database.php index 45d7c4a..5cad7a2 100644 --- a/vendor/JKingWeb/NewsSync/Database.php +++ b/vendor/JKingWeb/NewsSync/Database.php @@ -166,33 +166,34 @@ class Database { return true; } - public function userExists(string $username): bool { - return (bool) $this->db->prepare("SELECT count(*) from newssync_users where id is ?", "str")->run($username)->getSingle(); + public function userExists(string $user): bool { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + return (bool) $this->db->prepare("SELECT count(*) from newssync_users where id is ?", "str")->run($user)->getSingle(); } - public function userAdd(string $username, string $password = null): bool { + public function userAdd(string $user, string $password = null): bool { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + if($this->userExists($user)) return false; if(strlen($password) > 0) $password = password_hash($password, \PASSWORD_DEFAULT); - if($this->db->prepare("SELECT count(*) from newssync_users")->run()->getSingle() < 1) { //if there are no users, the first user should be made a global admin - $admin = "global"; - } else { - $admin = null; - } - $this->db->prepare("INSERT INTO newssync_users(id,password,admin) values(?,?,?)", "str", "str", "str")->run($username,$password,$admin); + $this->db->prepare("INSERT INTO newssync_users(id,password) values(?,?)", "str", "str", "str")->run($user,$password,$admin); return true; } - public function userRemove(string $username): bool { - $this->db->prepare("DELETE from newssync_users where id is ?", "str")->run($username); + public function userRemove(string $user): bool { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + $this->db->prepare("DELETE from newssync_users where id is ?", "str")->run($user); return true; } public function userList(string $domain = null): array { if($domain !== null) { + if(!$this->data->user->authorize("@".$domain, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $domain]); $domain = str_replace(["\\","%","_"],["\\\\", "\\%", "\\_"], $domain); $domain = "%@".$domain; $set = $this->db->prepare("SELECT id from newssync_users where id like ?", "str")->run($domain); } else { - $set = $this->db->query("SELECT id from newssync_users"); + if(!$this->data->user->authorize("", __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => "all users"]); + $set = $this->db->prepare("SELECT id from newssync_users")->run(); } $out = []; foreach($set as $row) { @@ -201,45 +202,74 @@ class Database { return $out; } - public function userPasswordSet($username, $password): bool { - if(!$this->userExists($username)) return false; + public function userPasswordGet(string $user): string { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + if(!$this->userExists($user)) return ""; + return (string) $this->db->prepare("SELECT password from newssync_users where id is ?", "str")->run($user)->getSingle(); + } + + public function userPasswordSet(string $user, string $password = null): bool { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + if(!$this->userExists($user)) return false; if(strlen($password > 0)) $password = password_hash($password); - $this->db->prepare("UPDATE newssync_users set password = ? where id is ?", "str", "str")->run($password, $username); + $this->db->prepare("UPDATE newssync_users set password = ? where id is ?", "str", "str")->run($password, $user); return true; } - public function userPropertiesGet(string $username): array { - $prop = $this->db->prepare("SELECT name,admin from newssync_users where id is ?", "str")->run($username)->get(); + public function userPropertiesGet(string $user): array { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + $prop = $this->db->prepare("SELECT name,rights from newssync_users where id is ?", "str")->run($user)->get(); if(!$prop) return []; return $prop; } - public function userPropertiesSet(string $username, array &$properties): array { + public function userPropertiesSet(string $user, array &$properties): array { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); $valid = [ // FIXME: add future properties "name" => "str", - "admin" => "str", ]; + if(!$this->userExists($user)) return []; $this->db->begin(); foreach($valid as $prop => $type) { if(!array_key_exists($prop, $properties)) continue; - $this->db->prepare("UPDATE newssync_users set $prop = ? where id is ?", $type, "str")->run($properties[$prop], $username); + $this->db->prepare("UPDATE newssync_users set $prop = ? where id is ?", $type, "str")->run($properties[$prop], $user); } $this->db->commit(); - return $this->userPropertiesGet($username); + return $this->userPropertiesGet($user); + } + + public function userRightsGet(string $user): int { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + return (int) $this->db->prepare("SELECT rights from newssync_users where id is ?", "str")->run($user)->getSingle(); + } + + public function userRightsSet(string $user, int $rights): bool { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + if(!$this->userExists($user)) return false; + $this->db->prepare("UPDATE newssync_users set rights = ? where id is ?", "int", "str")->run($rights, $user); + return true; } public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int { + if(!$this->data->user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + if(!$this->userExists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); $this->db->begin(); $qFeed = $this->db->prepare("SELECT id from newssync_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); - $id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); - if($id===null) { + $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); + if($feed===null) { $this->db->prepare("INSERT INTO newssync_feeds(url,username,password) values(?,?,?)", "str", "str", "str")->run($url, $fetchUser, $fetchPassword); - $id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); - var_export($id); + $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); } - $this->db->prepare("INSERT INTO newssync_subscriptions(owner,feed) values(?,?)", "str", "int")->run($user,$id); + $this->db->prepare("INSERT INTO newssync_subscriptions(owner,feed) values(?,?)", "str", "int")->run($user,$feed); + $sub = $this->db->prepare("SELECT id from newssync_subscriptions where owner is ? and feed is ?", "str", "int")->run($user, $feed)->getSingle(); $this->db->commit(); - return 0; + return $sub; + } + + public function subscriptionRemove(int $id): bool { + $this->db->begin(); + $feed = $this->db->prepare("SELECT feed from newssync_subscriptions where id is ?", "int")->run($id)->getSingle(); + $this->db->prepare("DELETE from newssync_subscriptions where id is ?", "int")->run($id); } } \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/Db/Common.php b/vendor/JKingWeb/NewsSync/Db/Common.php index f58b767..ceb64eb 100644 --- a/vendor/JKingWeb/NewsSync/Db/Common.php +++ b/vendor/JKingWeb/NewsSync/Db/Common.php @@ -6,7 +6,7 @@ use JKingWeb\DrUUID\UUID as UUID; Trait Common { protected $transDepth = 0; - public function schemaVersion(): integer { + public function schemaVersion(): int { try { return $this->data->db->settingGet("schema_version"); } catch(\Throwable $e) { diff --git a/vendor/JKingWeb/NewsSync/Exception.php b/vendor/JKingWeb/NewsSync/Exception.php index 02b2c7b..8d946d9 100644 --- a/vendor/JKingWeb/NewsSync/Exception.php +++ b/vendor/JKingWeb/NewsSync/Exception.php @@ -35,6 +35,7 @@ class Exception extends \Exception { "User/Exception.alreadyExists" => 10403, "User/Exception.authMissing" => 10411, "User/Exception.authFailed" => 10412, + "User/Exception.notAuthorized" => 10421, ]; public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { diff --git a/vendor/JKingWeb/NewsSync/User.php b/vendor/JKingWeb/NewsSync/User.php index 0eba9bb..b9b4690 100644 --- a/vendor/JKingWeb/NewsSync/User.php +++ b/vendor/JKingWeb/NewsSync/User.php @@ -7,7 +7,9 @@ class User { protected $data; protected $u; - protected $logged = []; + protected $authz = true; + protected $existSupported = 0; + protected $authzSupported = 0; static public function listDrivers(): array { $sep = \DIRECTORY_SEPARATOR; @@ -27,6 +29,8 @@ class User { $this->data = $data; $driver = $data->conf->userDriver; $this->u = $driver::create($data); + $this->existSupported = $this->u->driverFunctions("userExists"); + $this->authzSupported = $this->u->driverFunctions("authorize"); } public function __toString() { @@ -52,11 +56,11 @@ class User { if($_SERVER['PHP_AUTH_USER']) { $out = ["user" => $_SERVER['PHP_AUTH_USER'], "password" => $_SERVER['PHP_AUTH_PW']]; } else if($_SERVER['REMOTE_USER']) { - $out = ["user" => $_SERVER['REMOTE_USER'], "password" => null]; + $out = ["user" => $_SERVER['REMOTE_USER'], "password" => ""]; } else { - $out = ["user" => null, "password" => null]; + $out = ["user" => "", "password" => ""]; } - if($this->data->conf->userComposeNames && $out["user"] !== null) { + if($this->data->conf->userComposeNames && $out["user"] != "") { $out["user"] = $this->composeName($out["user"]); } $this->id = $out["user"]; @@ -65,18 +69,14 @@ class User { public function auth(string $user = null, string $password = null): bool { if($user===null) { - if($this->data->conf->userAuthPreferHTTP) { - return $this->authHTTP(); - } else { - return $this->authForm(); - } + if($this->data->conf->userAuthPreferHTTP) return $this->authHTTP(); + return $this->authForm(); } else { if($this->u->auth($user, $password)) { - $this->authPostprocess($user); + $this->authPostProcess($user, $password); return true; - } else { - return false; } + return false; } } @@ -84,7 +84,7 @@ class User { $cred = $this->credentialsForm(); if(!$cred["user"]) return $this->challengeForm(); if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeForm(); - $this->authPostprocess($cred["user"]); + $this->authPostProcess($cred["user"], $cred["password"]); return true; } @@ -92,7 +92,7 @@ class User { $cred = $this->credentialsHTTP(); if(!$cred["user"]) return $this->challengeHTTP(); if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeHTTP(); - $this->authPostprocess($cred["user"]); + $this->authPostProcess($cred["user"], $cred["password"]); return true; } @@ -101,19 +101,50 @@ class User { } public function list(string $domain = null): array { - if($this->u->driveFunctions("userList") != Driver::FUNC_NOT_IMPLEMENTED) { + if($this->u->driverFunctions("userList")==User\Driver::FUNC_EXTERNAL) { + if($domain===null) { + if(!$this->data->user->authorize("@".$domain, "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => $domain]); + } else { + if(!$this->data->user->authorize("", "userList")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userList", "user" => "all users"]); + } return $this->u->userList($domain); } else { - // N.B. this does not do any authorization checks return $this->data->db->userList($domain); } } + public function authorize(string $affectedUser, string $action, int $promoteLevel = 0): bool { + if(!$this->authz) return true; + if($this->id===null) $this->credentials(); + if($this->authzSupported) return $this->u->authorize($affectedUser, $action, $promoteLevel); + // if the driver does not implement authorization, only allow operation for the current user (this means no new users can be added) + if($affectedUser==$this->id && $action != "userRightsSet") return true; + return false; + } + + public function authorizationEnabled(bool $setting = null): bool { + if($setting===null) return $this->authz; + $this->authz = $setting; + return $setting; + } + public function exists(string $user): bool { - return $this->u->userExists($user); + if($this->u->driverFunctions("userExists") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userExists")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userExists", "user" => $user]); + } + if(!$this->existSupported) return true; + $out = $this->u->userExists($user); + if($out && $this->existSupported==User\Driver::FUNC_EXTERNAL && !$this->data->db->userExist($user)) { + try {$this->data->db->userAdd($user);} catch(\Throwable $e) {} + } + return $out; } public function add($user, $password = null): bool { + if($this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userAdd")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userAdd", "user" => $user]); + } + if($this->exists($user)) throw new User\Exception("alreadyExists", ["user" => $user, "action" => "userAdd"]); $out = $this->u->userAdd($user, $password); if($out && $this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) { try { @@ -124,6 +155,10 @@ class User { } public function remove(string $user): bool { + if($this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userRemove")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRemove", "user" => $user]); + } + if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userRemove"]); $out = $this->u->userRemove($user); if($out && $this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) { try { @@ -134,17 +169,57 @@ class User { } public function passwordSet(string $user, string $password): bool { + if($this->u->driverFunctions("userPasswordSet") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userPasswordSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPasswordSet", "user" => $user]); + } + if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPasswordSet"]); return $this->u->userPasswordSet($user, $password); } public function propertiesGet(string $user): array { - return $this->u->userPropertiesGet($user); + if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userPropertiesGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesGet", "user" => $user]); + } + if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPropertiesGet"]); + $domain = null; + if($this->data->conf->userComposeNames) $domain = substr($user,strrpos($user,"@")+1); + $init = [ + "id" => $user, + "name" => $user, + "rights" => User\Driver::RIGHTS_NONE, + "domain" => $domain + ]; + if($this->u->driverFunctions("userPropertiesGet") != User\Driver::FUNC_NOT_IMPLEMENTED) { + return array_merge($init, $this->u->userPropertiesGet($user)); + } + return $init; } public function propertiesSet(string $user, array $properties): array { + if($this->u->driverFunctions("userPropertiesSet") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userPropertiesSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userPropertiesSet", "user" => $user]); + } + if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPropertiesSet"]); return $this->u->userPropertiesSet($user, $properties); } + public function rightsGet(string $user): int { + if($this->u->driverFunctions("userRightsGet") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userRightsGet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsGet", "user" => $user]); + } + // we do not throw an exception here if the user does not exist, because it makes no material difference + if(!$this->exists($user)) return User\Driver::RIGHTS_NONE; + return $this->u->userRightsGet($user); + } + + public function rightsSet(string $user, int $level): bool { + if($this->u->driverFunctions("userRightsSet") != User\Driver::FUNC_INTERNAL) { + if(!$this->data->user->authorize($user, "userRightsSet")) throw new User\ExceptionAuthz("notAuthorized", ["action" => "userRightsSet", "user" => $user]); + } + if(!$this->exists($user)) throw new User\Exception("doesNotExist", ["user" => $user, "action" => "userPromote"]); + return $this->u->userRightsSet($user, $level); + } + // FIXME: stubs public function challenge(): bool {throw new User\Exception("authFailed");} public function challengeForm(): bool {throw new User\Exception("authFailed");} @@ -158,7 +233,11 @@ class User { } } - protected function authPostprocess(string $user): bool { + protected function authPostprocess(string $user, string $password): bool { + if($this->u->driverFunctions("auth") != User\Driver::FUNC_INTERNAL && !$this->data->db->userExists($user)) { + if($password=="") $password = null; + try {$this->data->db->userAdd($user, $password);} catch(\Throwable $e) {} + } return true; } } \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/User/Driver.php b/vendor/JKingWeb/NewsSync/User/Driver.php index 0d0ff72..2e06f7a 100644 --- a/vendor/JKingWeb/NewsSync/User/Driver.php +++ b/vendor/JKingWeb/NewsSync/User/Driver.php @@ -7,10 +7,17 @@ Interface Driver { const FUNC_INTERNAL = 1; const FUNC_EXTERNAL = 2; + const RIGHTS_NONE = 0; + const RIGHTS_DOMAIN_MANAGER = 25; + const RIGHTS_DOMAIN_ADMIN = 50; + const RIGHTS_GLOBAL_MANAGER = 75; + const RIGHTS_GLOBAL_ADMIN = 100; + static function create(\JKingWeb\NewsSync\RuntimeData $data): Driver; static function driverName(): string; function driverFunctions(string $function = null); function auth(string $user, string $password): bool; + function authorize(string $affectedUser, string $action); function userExists(string $user): bool; function userAdd(string $user, string $password = null): bool; function userRemove(string $user): bool; @@ -18,4 +25,6 @@ Interface Driver { function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool; function userPropertiesGet(string $user): array; function userPropertiesSet(string $user, array $properties): array; + function userRightsGet(string $user): int; + function userRightsSet(string $user, int $level): bool; } \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/User/DriverInternal.php b/vendor/JKingWeb/NewsSync/User/DriverInternal.php index ef5b917..fbbe22e 100644 --- a/vendor/JKingWeb/NewsSync/User/DriverInternal.php +++ b/vendor/JKingWeb/NewsSync/User/DriverInternal.php @@ -9,6 +9,7 @@ class DriverInternal implements Driver { protected $db; protected $functions = [ "auth" => Driver::FUNC_INTERNAL, + "authorize" => Driver::FUNC_INTERNAL, "userList" => Driver::FUNC_INTERNAL, "userExists" => Driver::FUNC_INTERNAL, "userAdd" => Driver::FUNC_INTERNAL, @@ -16,6 +17,8 @@ class DriverInternal implements Driver { "userPasswordSet" => Driver::FUNC_INTERNAL, "userPropertiesGet" => Driver::FUNC_INTERNAL, "userPropertiesSet" => Driver::FUNC_INTERNAL, + "userRightsGet" => Driver::FUNC_INTERNAL, + "userRightsSet" => Driver::FUNC_INTERNAL, ]; static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver { diff --git a/vendor/JKingWeb/NewsSync/User/ExceptionAuthz.php b/vendor/JKingWeb/NewsSync/User/ExceptionAuthz.php new file mode 100644 index 0000000..0370a4d --- /dev/null +++ b/vendor/JKingWeb/NewsSync/User/ExceptionAuthz.php @@ -0,0 +1,6 @@ +userExists($user)) return false; - return true; + if(!$this->data->user->exists($user)) return false; $hash = $this->db->userPasswordGet($user); if(!$hash) return false; return password_verify($password, $hash); } + function authorize(string $affectedUser, string $action, int $newRightsLevel = 0): bool { + // if the affected user is the actor and the actor is not trying to grant themselves rights, accept the request + if($affectedUser==$this->data->user->id && $action != "userRightsSet") return true; + // get properties of actor if not already available + if(!sizeof($this->actor)) $this->actor = $this->data->user->propertiesGet($this->data->user->id); + $rights =& $this->actor["rights"]; + // if actor is a global admin, accept the request + if($rights==self::RIGHTS_GLOBAL_ADMIN) return true; + // if actor is a common user, deny the request + if($rights==self::RIGHTS_NONE) return false; + // if actor is not some other sort of admin, deny the request + if(!in_array($rights,[self::RIGHTS_GLOBAL_MANAGER,self::RIGHTS_DOMAIN_MANAGER,self::RIGHTS_DOMAIN_ADMIN],true)) return false; + // if actor is a domain admin/manager and domains don't match, deny the request + if($this->data->conf->userComposeNames && $this->actor["domain"] && $rights != self::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 (or equal to your own, for managers) is not allowed + if($newRightsLevel > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $newRightsLevel==$rights)) return false; + } + $affectedRights = $this->data->user->rightsGet($affectedUser); + // acting for users with rights greater than your own (or equal, for managers) is not allowed + if($affectedRights > $rights || ($rights != self::RIGHTS_DOMAIN_ADMIN && $affectedRights==$rights)) return false; + return true; + } + function userExists(string $user): bool { return $this->db->userExists($user); } function userAdd(string $user, string $password = null): bool { - if($this->userExists($user)) throw new Exception("alreadyExists", ["user" => $user, "action" => __FUNCTION__]); - // FIXME: add authorization checks return $this->db->userAdd($user, $password); } function userRemove(string $user): bool { - if(!$this->userExists($user)) throw new Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); - // FIXME: add authorization checks return $this->db->userRemove($user); } function userList(string $domain = null): array { - // FIXME: add authorization checks return $this->db->userList($domain); } function userPasswordSet(string $user, string $newPassword, string $oldPassword): bool { - if(!$this->userExists($user)) throw new Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); - // FIXME: add authorization checks return $this->db->userPasswordSet($user, $newPassword); } function userPropertiesGet(string $user): array { - if(!$this->userExists($user)) throw new Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); - // FIXME: add authorization checks return $this->db->userPropertiesGet($user); } function userPropertiesSet(string $user, array $properties): array { - if(!$this->userExists($user)) throw new Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); - // FIXME: add authorization checks return $this->db->userPropertiesSet($user, $properties); } + + function userRightsGet(string $user): int { + return $this->db->userRightsGet($user); + } + + function userRightsSet(string $user, int $level): bool { + return $this->db->userRightsSet($user, $level); + } } \ No newline at end of file