<?php
/*
* SCROTT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to UNLICENSE
*/
require_once "class/agent.class.php";
/*
* This class models Scrott users, including users without valid logins
* (external-users).
*/
class user extends agent
{
/*
* Constructor
*/
public function __construct(?string $guid = NULL)
{
$this->fields['users'] = array(
"guid",
"auth",
"salt",
"alias",
"email",
"emailVer",
"admin",
"reg",
"emailConf",
);
parent::__construct($guid);
$this->expectType("user");
}
/*
* Get the GUID of a user object from a given username, or NULL if
* the username is not in use. Therefore, this function can be
* used to test the existence of a user with the given username.
*/
public static function getGuidByUname(string $uname) : ?string
{
$uname = database::esc($uname);
$query = "SELECT guid FROM objects WHERE objtype = 'user' AND name = '" . $uname . "'";
$res = database::query($query);
if (count($res) == 0)
return NULL;
return $res[0]['guid'];
}
/*
* Get a user object from a given username, or NULL if the username
* is not in use. This function can be used to test the existence
* of a user with the given username.
*/
public static function getByUname(string $uname) : ?user
{
if (($guid = self::getGuidByUname($uname)))
return new user($guid);
return NULL;
}
/*
* Get an array of all users, sorted by username
*/
public static function getAll_ordByUname() : array
{
$query = "SELECT guid FROM objects WHERE objtype = 'user' ORDER BY name";
$res = database::query($query);
$users = array();
foreach ($res as $u)
$users[] = new user($u['guid']);
return $users;
}
/*
* Get an array of all users, sorted by admin (descending, admins
* first), then by username.
*/
public static function getAll_ordByAdminByUname() : array
{
$query = "SELECT o.guid FROM objects o JOIN users u ON o.guid = u.guid " .
"WHERE o.objtype = 'user' ORDER BY u.admin DESC, o.name";
$res = database::query($query);
$users = array();
foreach ($res as $u)
$users[] = new user($u['guid']);
return $users;
}
/*
* Get an array of all admins, sorted by username
*/
public static function getAllAdmin_ordByUname() : array
{
$query = "SELECT o.guid FROM objects o JOIN users u ON o.guid = u.guid " .
"WHERE o.objtype = 'user' AND u.admin = 1 ORDER BY o.name";
$res = database::query($query);
$users = array();
foreach ($res as $u)
$users[] = new user($u['guid']);
return $users;
}
/*
* Get the currently logged in user, or NULL if logged out. This
* function will throw if unable to aquire a PHP session. This
* function will also forcibly log the current user out if it
* detects any changes in the user-agent or remote IP address.
*/
public static function getCurrent() : ?user
{
if ((session_status() != PHP_SESSION_ACTIVE) && !session_start())
throw new Exception("Unable to aquire a PHP session");
if (!isset($_SESSION['userguid']))
return NULL;
/* detect session hijacking */
if (($_SESSION['useragent'] != $_SERVER['HTTP_USER_AGENT']) ||
($_SESSION['userip'] != $_SERVER['REMOTE_ADDR']))
{
self::setCurrent();
location("/");
return NULL;
}
try
{
return new user($_SESSION['userguid']);
}
catch (Exception $e)
{
/* invalid user */
self::setCurrent();
location("/");
return NULL;
}
}
/*
* Set the currently logged in user. Using NULL will logout any
* current user. This function will throw if unable to aquire a
* PHP session. This function will also cache the user-agent and
* remote IP address of the current request to help validate future
* requests made under the same session.
*/
public static function setCurrent(?user $user = NULL) : void
{
if ((session_status() != PHP_SESSION_ACTIVE) && !session_start())
throw new Exception("Unable to aquire a PHP session");
unset($_SESSION['userguid']);
unset($_SESSION['useragent']);
unset($_SESSION['userip']);
if ($user)
{
$_SESSION['userguid'] = $user->guid;
$_SESSION['useragent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['userip'] = $_SERVER['REMOTE_ADDR'];
}
}
/*
* Initialize a new user object with the given username and plain
* text password. This function returns NULL if $uname is already
* being used.
*/
public static function initNew(string $uname, string $passwd) : ?user
{
if (self::getByUname($uname))
return NULL;
$user = new user();
/* if there exist no users already, make this new one an admin */
if (count(self::getAll_ordByUname()) == 0)
$user->admin = 1;
$user->name = $uname;
$user->objtype = "user";
$user->setPasswd($passwd);
$user->setEmail("");
$user->reg = 1;
$user->saveObj();
return $user;
}
/*
* Get the salted and hashed form of a password
*/
private static function getAuth(string $passwd, string $salt) : string
{
return hash("sha256", $passwd . $salt);
}
/*
* Validate the given plain-text password for this user. Returns true if
* correct, false otherwise.
*/
public function validatePasswd(string $passwd) : bool
{
$auth = self::getAuth($passwd, $this->salt);
return $auth == $this->auth;
}
/*
* Update the auth and salt for this user, given a new plain-text
* password.
*/
public function setPasswd(string $passwd) : void
{
$this->salt = self::getBlob();
$this->auth = self::getAuth($passwd, $this->salt);
$this->saveObj();
}
/*
* Validate the email confirmation code for this user. Returns true if
* correct, false otherwise. On success, $this->emailConf is also set
* to 1
*/
public function verifyEmail(string $ver) : bool
{
if ($ver != $this->emailVer)
return false;
$this->emailConf = 1;
$this->saveObj();
return true;
}
/*
* Update the email address for this user. This function will automatically
* reset the emailConf flag and confirmation code for this user as well.
*/
public function setEmail(string $email) : void
{
$this->email = $email;
$this->emailVer = substr(self::getBlob(), 0, 8);
$this->emailConf = 0;
$this->saveObj();
}
/*
* Get all groups this user owns or is a member of. This isn't necessarily
* all groups this user cas access permissions for. Results are sorted by
* ownership, then by name.
*/
public function getGroups_ordByOwnByName() : array
{
$groups = array();
/* owner */
$query = "SELECT guid FROM objects WHERE objtype = 'group' AND owner = '" . database::esc($this->guid) . "' " .
"ORDER BY name";
$res = database::query($query);
foreach ($res as $g)
$groups[] = new group($g['guid']);
/* member */
$query = "SELECT o.guid FROM objects o JOIN members m ON o.guid = m.guid WHERE o.objtype = 'group' AND " .
"m.member = '" . database::esc($this->guid) . "' ORDER BY o.name";
$res = database::query($query);
foreach ($res as $g)
$groups[] = new group($g['guid']);
return $groups;
}
/*
* Send an email message to this user using stored configuration
* parameters. If config is not established, delivery is not
* attempted. Return status.
*/
public function sendEmail(string $subj, string $mesg,
?string $attachPath = NULL, ?string $attachName = NULL,
bool $ignoreEmailConf = false) : bool
{
if (settings::smtpServer() == "")
return false;
if (!$ignoreEmailConf && !$this->emailConf)
return true;
if ($this->email == "")
return true;
$mail = new PHPMailer();
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->Host = settings::smtpServer();
$mail->Port = settings::smtpPort();
$mail->Username = settings::smtpUname();
$mail->Password = settings::smtpPasswd();
$mail->SMTPSecure = settings::smtpSecurity();
$mail->setFrom(settings::smtpEmailAddress(), settings::smtpFrom());
$mail->addAddress($this->email, $this->getDisplayName());
$mail->Subject = $subj;
$mail->Body = $mesg;
if ($attachPath && $attachName)
$mail->addAttachment($attachPath, $attachName);
return $mail->send();
}
}
?>