From 646b44c9cf60cfb33e245066f0b3c13dd4c4d6f5 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 28 Oct 2016 08:27:35 -0400 Subject: [PATCH] Functioning (but still incomplete) user management --- locale/en.php | 66 +++---- sql/SQLite3/0.sql | 13 +- vendor/JKingWeb/NewsSync/Auth/Driver.php | 10 -- .../JKingWeb/NewsSync/Auth/DriverInternal.php | 24 --- vendor/JKingWeb/NewsSync/Conf.php | 8 +- vendor/JKingWeb/NewsSync/Database.php | 74 +++++++- vendor/JKingWeb/NewsSync/Db/Common.php | 10 +- vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php | 6 +- vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php | 4 +- vendor/JKingWeb/NewsSync/Db/ResultSQLite3.php | 6 +- .../JKingWeb/NewsSync/Db/StatementSQLite3.php | 2 +- vendor/JKingWeb/NewsSync/Exception.php | 17 ++ vendor/JKingWeb/NewsSync/ExceptionFatal.php | 9 + vendor/JKingWeb/NewsSync/Lang.php | 11 +- vendor/JKingWeb/NewsSync/RuntimeData.php | 2 +- vendor/JKingWeb/NewsSync/User.php | 164 ++++++++++++++++++ vendor/JKingWeb/NewsSync/User/Driver.php | 21 +++ .../JKingWeb/NewsSync/User/DriverInternal.php | 42 +++++ vendor/JKingWeb/NewsSync/User/Exception.php | 6 + .../NewsSync/User/InternalFunctions.php | 52 ++++++ 20 files changed, 454 insertions(+), 93 deletions(-) delete mode 100644 vendor/JKingWeb/NewsSync/Auth/Driver.php delete mode 100644 vendor/JKingWeb/NewsSync/Auth/DriverInternal.php create mode 100644 vendor/JKingWeb/NewsSync/ExceptionFatal.php create mode 100644 vendor/JKingWeb/NewsSync/User.php create mode 100644 vendor/JKingWeb/NewsSync/User/Driver.php create mode 100644 vendor/JKingWeb/NewsSync/User/DriverInternal.php create mode 100644 vendor/JKingWeb/NewsSync/User/Exception.php create mode 100644 vendor/JKingWeb/NewsSync/User/InternalFunctions.php diff --git a/locale/en.php b/locale/en.php index f1f18d28..4897f5e9 100644 --- a/locale/en.php +++ b/locale/en.php @@ -1,41 +1,47 @@ "Default language file \"{0}\" missing", - "Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing" => "Language file \"{0}\" is not available", - "Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable" => "Insufficient permissions to read language file \"{0}\"", - "Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt" => "Language file \"{0}\" is corrupt or does not conform to expected format", - "Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing" => "Message string \"{msgID}\" missing from all loaded language files ({fileList})", - "Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid" => "Message string \"{msgID}\" is not a valid ICU message string (language files loaded: {fileList})", + 'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing', + 'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available', + 'Exception.JKingWeb/NewsSync/Lang/Exception.fileUnreadable' => 'Insufficient permissions to read language file "{0}"', + 'Exception.JKingWeb/NewsSync/Lang/Exception.fileCorrupt' => 'Language file "{0}" is corrupt or does not conform to expected format', + 'Exception.JKingWeb/NewsSync/Lang/Exception.stringMissing' => 'Message string "{msgID}" missing from all loaded language files ({fileList})', + 'Exception.JKingWeb/NewsSync/Lang/Exception.stringInvalid' => 'Message string "{msgID}" is not a valid ICU message string (language files loaded: {fileList})', - "Exception.JKingWeb/NewsSync/Conf/Exception.fileMissing" => "Configuration file \"{0}\" does not exist", - "Exception.JKingWeb/NewsSync/Conf/Exception.fileUnreadable" => "Insufficient permissions to read configuration file \"{0}\"", - "Exception.JKingWeb/NewsSync/Conf/Exception.fileUncreatable" => "Insufficient permissions to write new configuration file \"{0}\"", - "Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable" => "Insufficient permissions to overwrite configuration file \"{0}\"", - "Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt" => "Configuration file \"{0}\" is corrupt or does not conform to expected format", + 'Exception.JKingWeb/NewsSync/Conf/Exception.fileMissing' => 'Configuration file "{0}" does not exist', + 'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnreadable' => 'Insufficient permissions to read configuration file "{0}"', + 'Exception.JKingWeb/NewsSync/Conf/Exception.fileUncreatable' => 'Insufficient permissions to write new configuration file "{0}"', + 'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable' => 'Insufficient permissions to overwrite configuration file "{0}"', + 'Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt' => 'Configuration file "{0}" is corrupt or does not conform to expected format', - "Exception.JKingWeb/NewsSync/Db/Exception.extMissing" => "Required PHP extension for driver \"{0}\" not installed", - "Exception.JKingWeb/NewsSync/Db/Exception.fileMissing" => "Database file \"{0}\" does not exist", - "Exception.JKingWeb/NewsSync/Db/Exception.fileUnreadable" => "Insufficient permissions to open database file \"{0}\" for reading", - "Exception.JKingWeb/NewsSync/Db/Exception.fileUnwritable" => "Insufficient permissions to open database file \"{0}\" for writing", - "Exception.JKingWeb/NewsSync/Db/Exception.fileUnusable" => "Insufficient permissions to open database file \"{0}\" for reading or writing", - "Exception.JKingWeb/NewsSync/Db/Exception.fileUncreatable" => "Insufficient permissions to create new database file \"{0}\"", - "Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt" => "Database file \"{0}\" is corrupt or not a valid database", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.manual" => - "{from_version, select, + 'Exception.JKingWeb/NewsSync/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileMissing' => 'Database file "{0}" does not exist', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"', + 'Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database', + + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.manual' => + '{from_version, select, 0 {{driver_name} database is configured for manual updates and is not initialized; please populate the database with the base schema} other {{driver_name} database is configured for manual updates; please update from schema version {current} to version {target}} - }", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly" => - "{from_version, select, + }', + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly' => + '{from_version, select, 0 {{driver_name} database must be updated manually and is not initialized; please populate the database with the base schema} other {{driver_name} database must be updated manually; please update from schema version {current} to version {target}} - }", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.missing" => "Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.unreadable" => "Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.unusable" => "Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}", - "Exception.JKingWeb/NewsSync/Db/Update/Exception.tooNew" => - "{difference, select, + }', + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available', + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}', + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}', + 'Exception.JKingWeb/NewsSync/Db/Update/Exception.tooNew' => + '{difference, select, 0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}} other {Automatic updating of the {driver_name} database failed because its version, {current}, is newer than the requested version, {target}} - }", + }', + + 'Exception.JKingWeb/NewsSync/User/Exception.alreadyExists' => 'Could not perform action "{action}" because the user {user} already exists', + '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', ]; \ No newline at end of file diff --git a/sql/SQLite3/0.sql b/sql/SQLite3/0.sql index 7765ee13..0d8cbb2a 100644 --- a/sql/SQLite3/0.sql +++ b/sql/SQLite3/0.sql @@ -9,8 +9,8 @@ create table newssync_feeds( modified datetime not null default CURRENT_TIMESTAMP, -- 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, -- HTTP authentication username - password TEXT, -- HTTP authentication password (this is stored in plain text) + username TEXT not null default '', -- HTTP authentication username + password TEXT not null default '', -- HTTP authentication password (this is stored in plain text) unique(url,username,password) -- a URL with particular credentials should only appear once ); @@ -59,9 +59,12 @@ create table newssync_users( id TEXT primary key not null, -- user id password TEXT, -- password, salted and hashed; if using external authentication this would be blank name TEXT, -- display name - avatar_type TEXT, -- avatar image's MIME content type - avatar_data BLOB, -- avatar image's binary data - admin boolean not null default 0 -- whether the user is an administrator + 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 ); -- TT-RSS categories and ownCloud folders diff --git a/vendor/JKingWeb/NewsSync/Auth/Driver.php b/vendor/JKingWeb/NewsSync/Auth/Driver.php deleted file mode 100644 index 1a676f7e..00000000 --- a/vendor/JKingWeb/NewsSync/Auth/Driver.php +++ /dev/null @@ -1,10 +0,0 @@ -data = $data; - $driver = $data->conf->dbClass; + $driver = $data->conf->dbDriver; $this->db = $driver::create($data, INSTALL); $ver = $this->db->schemaVersion(); if(!INSTALL && $ver < self::SCHEMA_VERSION) { @@ -161,20 +161,78 @@ class Database { $this->db->prepare("REPLACE INTO newssync_settings(key,value,type) values(?,?,?)", "str", (($type=="null") ? "null" : "str"), "str")->run($key, $value, "text"); } - public function settingClear(string $key): bool { + public function settingRemove(string $key): bool { $this->db->prepare("DELETE from newssync_settings where key = ?", "str")->run($key); return true; } - public function userAdd(string $username, string $password = null, bool $admin = false): string { - $this->db->prepare("INSERT INTO newssync_users(id,password,admin) values(?,?,?)", "str", "str", "bool")->run($username,$password,$admin); - return $username; + 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 subscriptionAdd(string $user, string $url, string $fetchUser = null, string $fetchPassword = null): int { + public function userAdd(string $username, string $password = null): bool { + 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); + return true; + } + + public function userRemove(string $username): bool { + $this->db->prepare("DELETE from newssync_users where id is ?", "str")->run($username); + return true; + } + + public function userList(string $domain = null): array { + if($domain !== null) { + $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"); + } + $out = []; + foreach($set as $row) { + $out[] = $row["id"]; + } + return $out; + } + + public function userPasswordSet($username, $password): bool { + if(!$this->userExists($username)) 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); + 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(); + if(!$prop) return []; + return $prop; + } + + public function userPropertiesSet(string $username, array &$properties): array { + $valid = [ // FIXME: add future properties + "name" => "str", + "admin" => "str", + ]; + $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->commit(); + return $this->userPropertiesGet($username); + } + + public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int { $this->db->begin(); $qFeed = $this->db->prepare("SELECT id from newssync_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); - if(is_null($id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle())) { + $id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); + if($id===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); diff --git a/vendor/JKingWeb/NewsSync/Db/Common.php b/vendor/JKingWeb/NewsSync/Db/Common.php index de92561c..f58b7670 100644 --- a/vendor/JKingWeb/NewsSync/Db/Common.php +++ b/vendor/JKingWeb/NewsSync/Db/Common.php @@ -6,6 +6,14 @@ use JKingWeb\DrUUID\UUID as UUID; Trait Common { protected $transDepth = 0; + public function schemaVersion(): integer { + try { + return $this->data->db->settingGet("schema_version"); + } catch(\Throwable $e) { + return 0; + } + } + public function begin(): bool { $this->exec("SAVEPOINT newssync_".($this->transDepth)); $this->transDepth += 1; @@ -50,7 +58,7 @@ Trait Common { } public function unlock(): bool { - return $this->data->db->settingClear("lock"); + return $this->data->db->settingRemove("lock"); } public function isLocked(): bool { diff --git a/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php b/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php index 7ebc4b6d..66ad8589 100644 --- a/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php @@ -24,10 +24,10 @@ Trait CommonSQLite3 { $this->begin(); try { $file = $path.$a.".sql"; - if(!file_exists($file)) throw new Update\Exception("missing", ['file' => $file, 'driver_name' => $this->driverName()]); - if(!is_readable($file)) throw new Update\Exception("unreadable", ['file' => $file, 'driver_name' => $this->driverName()]); + if(!file_exists($file)) throw new Update\Exception("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]); + if(!is_readable($file)) throw new Update\Exception("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]); $sql = @file_get_contents($file); - if($sql===false) throw new Update\Exception("unusable", ['file' => $file, 'driver_name' => $this->driverName()]); + if($sql===false) throw new Update\Exception("fileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]); $this->exec($sql); } catch(\Throwable $e) { // undo any partial changes from the failed update diff --git a/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php b/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php index 3bd2333c..d3c1f313 100644 --- a/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php @@ -3,7 +3,9 @@ declare(strict_types=1); namespace JKingWeb\NewsSync\Db; class DriverSQLite3 implements Driver { - use Common, CommonSQLite3; + use Common, CommonSQLite3 { + CommonSQLite3::schemaVersion insteadof Common; + } protected $db; protected $data; diff --git a/vendor/JKingWeb/NewsSync/Db/ResultSQLite3.php b/vendor/JKingWeb/NewsSync/Db/ResultSQLite3.php index bd4ae3a3..ef1371cd 100644 --- a/vendor/JKingWeb/NewsSync/Db/ResultSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/ResultSQLite3.php @@ -3,12 +3,14 @@ declare(strict_types=1); namespace JKingWeb\NewsSync\Db; class ResultSQLite3 implements Result { + protected $st; protected $set; protected $pos = 0; protected $cur = null; - public function __construct(\SQLite3Result $resultObj) { - $this->set = $resultObj; + public function __construct($result, $statement = null) { + $this->st = $statement; //keeps the statement from being destroyed, invalidating the result set + $this->set = $result; } public function __destruct() { diff --git a/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php b/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php index 5c15ada2..7b983371 100644 --- a/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php @@ -66,6 +66,6 @@ class StatementSQLite3 implements Statement { } $this->st->bindParam($a+1, $values[$a], $type); } - return new ResultSQLite3($this->st->execute()); + return new ResultSQLite3($this->st->execute(), $this); } } \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/Exception.php b/vendor/JKingWeb/NewsSync/Exception.php index 08099d0a..02b2c7b8 100644 --- a/vendor/JKingWeb/NewsSync/Exception.php +++ b/vendor/JKingWeb/NewsSync/Exception.php @@ -18,6 +18,23 @@ class Exception extends \Exception { "Db/Exception.fileUnwritable" => 10205, "Db/Exception.fileUncreatable" => 10206, "Db/Exception.fileCorrupt" => 10207, + "Db/Update/Exception.tooNew" => 10211, + "Db/Update/Exception.fileMissing" => 10212, + "Db/Update/Exception.fileUnusable" => 10213, + "Db/Update/Exception.fileUnreadable" => 10214, + "Db/Update/Exception.manual" => 10215, + "Db/Update/Exception.manualOnly" => 10216, + "Conf/Exception.fileMissing" => 10302, + "Conf/Exception.fileUnusable" => 10303, + "Conf/Exception.fileUnreadable" => 10304, + "Conf/Exception.fileUnwritable" => 10305, + "Conf/Exception.fileUncreatable" => 10306, + "Conf/Exception.fileCorrupt" => 10307, + "User/Exception.functionNotImplemented" => 10401, + "User/Exception.doesNotExist" => 10402, + "User/Exception.alreadyExists" => 10403, + "User/Exception.authMissing" => 10411, + "User/Exception.authFailed" => 10412, ]; public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { diff --git a/vendor/JKingWeb/NewsSync/ExceptionFatal.php b/vendor/JKingWeb/NewsSync/ExceptionFatal.php new file mode 100644 index 00000000..40f7202e --- /dev/null +++ b/vendor/JKingWeb/NewsSync/ExceptionFatal.php @@ -0,0 +1,9 @@ + $msgID, 'fileList' => implode(", ",self::$loaded)]); // variables fed to MessageFormatter must be contained in array - if($vars !== null && !is_array($vars)) $vars = [$vars]; - $msg = \MessageFormatter::formatMessage(self::$locale, self::$strings[$msgID], $vars); + $msg = self::$strings[$msgID]; + if($vars===null) { + return $msg; + } else if(!is_array($vars)) { + $vars = [$vars]; + } + $msg = \MessageFormatter::formatMessage(self::$locale, $msg, $vars); if($msg===false) throw new Lang\Exception("stringInvalid", ['msgID' => $msgID, 'fileList' => implode(", ",self::$loaded)]); return $msg; } @@ -73,7 +78,7 @@ class Lang { } static protected function checkRequirements(): bool { - if(!extension_loaded("intl")) throw new FatalException("The \"Intl\" extension is required, but not loaded"); + if(!extension_loaded("intl")) throw new ExceptionFatal("The \"Intl\" extension is required, but not loaded"); self::$requirementsMet = true; return true; } diff --git a/vendor/JKingWeb/NewsSync/RuntimeData.php b/vendor/JKingWeb/NewsSync/RuntimeData.php index 5d0993e6..54ca06f7 100644 --- a/vendor/JKingWeb/NewsSync/RuntimeData.php +++ b/vendor/JKingWeb/NewsSync/RuntimeData.php @@ -11,6 +11,6 @@ class RuntimeData { $this->conf = $conf; Lang::set($conf->lang); $this->db = new Database($this); - //$this->auth = new Authenticator($this); + $this->user = new User($this); } } \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/User.php b/vendor/JKingWeb/NewsSync/User.php new file mode 100644 index 00000000..0eba9bb8 --- /dev/null +++ b/vendor/JKingWeb/NewsSync/User.php @@ -0,0 +1,164 @@ +data = $data; + $driver = $data->conf->userDriver; + $this->u = $driver::create($data); + } + + public function __toString() { + if($this->id===null) $this->credentials(); + return (string) $this->id; + } + + public function credentials(): array { + if($this->data->conf->userAuthPreferHTTP) { + return $this->credentialsHTTP(); + } else { + return $this->credentialsForm(); + } + } + + public function credentialsForm(): array { + // FIXME: stub + $this->id = "john.doe@example.com"; + return ["user" => "john.doe@example.com", "password" => "secret"]; + } + + public function credentialsHTTP(): array { + 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]; + } else { + $out = ["user" => null, "password" => null]; + } + if($this->data->conf->userComposeNames && $out["user"] !== null) { + $out["user"] = $this->composeName($out["user"]); + } + $this->id = $out["user"]; + return $out; + } + + 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(); + } + } else { + if($this->u->auth($user, $password)) { + $this->authPostprocess($user); + return true; + } else { + return false; + } + } + } + + public function authForm(): bool { + $cred = $this->credentialsForm(); + if(!$cred["user"]) return $this->challengeForm(); + if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeForm(); + $this->authPostprocess($cred["user"]); + return true; + } + + public function authHTTP(): bool { + $cred = $this->credentialsHTTP(); + if(!$cred["user"]) return $this->challengeHTTP(); + if(!$this->u->auth($cred["user"], $cred["password"])) return $this->challengeHTTP(); + $this->authPostprocess($cred["user"]); + return true; + } + + public function driverFunctions(string $function = null) { + return $this->u->driverFunctions($function); + } + + public function list(string $domain = null): array { + if($this->u->driveFunctions("userList") != Driver::FUNC_NOT_IMPLEMENTED) { + return $this->u->userList($domain); + } else { + // N.B. this does not do any authorization checks + return $this->data->db->userList($domain); + } + } + + public function exists(string $user): bool { + return $this->u->userExists($user); + } + + public function add($user, $password = null): bool { + $out = $this->u->userAdd($user, $password); + if($out && $this->u->driverFunctions("userAdd") != User\Driver::FUNC_INTERNAL) { + try { + if(!$this->data->db->userExists($user)) $this->data->db->userAdd($user, $password); + } catch(\Throwable $e) {} + } + return $out; + } + + public function remove(string $user): bool { + $out = $this->u->userRemove($user); + if($out && $this->u->driverFunctions("userRemove") != User\Driver::FUNC_INTERNAL) { + try { + if($this->data->db->userExists($user)) $this->data->db->userRemove($user); + } catch(\Throwable $e) {} + } + return $out; + } + + public function passwordSet(string $user, string $password): bool { + return $this->u->userPasswordSet($user, $password); + } + + public function propertiesGet(string $user): array { + return $this->u->userPropertiesGet($user); + } + + public function propertiesSet(string $user, array $properties): array { + return $this->u->userPropertiesSet($user, $properties); + } + + // FIXME: stubs + public function challenge(): bool {throw new User\Exception("authFailed");} + public function challengeForm(): bool {throw new User\Exception("authFailed");} + public function challengeHTTP(): bool {throw new User\Exception("authFailed");} + + protected function composeName(string $user): string { + if(preg_match("/.+?@[^@]+$/",$user)) { + return $user; + } else { + return $user."@".$_SERVER['HTTP_HOST']; + } + } + + protected function authPostprocess(string $user): bool { + return true; + } +} \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/User/Driver.php b/vendor/JKingWeb/NewsSync/User/Driver.php new file mode 100644 index 00000000..0d0ff72f --- /dev/null +++ b/vendor/JKingWeb/NewsSync/User/Driver.php @@ -0,0 +1,21 @@ + 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, + ]; + + static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver { + return new static($data); + } + + public function __construct(\JKingWeb\NewsSync\RuntimeData $data) { + $this->data = $data; + $this->db = $this->data->db; + } + + static public function driverName(): string { + return "Internal"; + } + + 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; + } + } +} \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/User/Exception.php b/vendor/JKingWeb/NewsSync/User/Exception.php new file mode 100644 index 00000000..ed4c7e5e --- /dev/null +++ b/vendor/JKingWeb/NewsSync/User/Exception.php @@ -0,0 +1,6 @@ +userExists($user)) return false; + return true; + $hash = $this->db->userPasswordGet($user); + if(!$hash) return false; + return password_verify($password, $hash); + } + + 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); + } +} \ No newline at end of file