427 lines
13 KiB
PHP
427 lines
13 KiB
PHP
<?php
|
|
namespace fox;
|
|
|
|
/**
|
|
*
|
|
*
|
|
* Class fox\user
|
|
*
|
|
* @copyright MX STAR LLC 2021
|
|
* @version 4.0.0
|
|
* @author Pavel Dmitriev
|
|
* @license GPLv3
|
|
*
|
|
*/
|
|
class user extends baseClass implements externalCallable
|
|
{
|
|
|
|
protected $id;
|
|
public $login;
|
|
protected UID $invCode;
|
|
protected $__secret;
|
|
public $authType;
|
|
public $authRefId;
|
|
public int $offlineAuthCtr = 0;
|
|
public $fullName;
|
|
protected bool $active = true;
|
|
protected bool $deleted = false;
|
|
public $companyId;
|
|
public $eMail;
|
|
public bool $eMailConfirmed = false;
|
|
protected array $config = [];
|
|
protected ?company $__company = null;
|
|
|
|
protected static $excludeProps = ["authRefId"];
|
|
public static $sqlTable = "tblUsers";
|
|
public static $deletedFieldName = "deleted";
|
|
|
|
public static $sqlColumns = [
|
|
"login" => [
|
|
"type" => "VARCHAR(255)",
|
|
"index" => "UNIQUE",
|
|
"nullable" => false,
|
|
"search"=>"like"
|
|
],
|
|
"secret" => [
|
|
"type" => "VARCHAR(255)",
|
|
"index" => "INDEX",
|
|
"nullable" => true
|
|
],
|
|
"invCode" => [
|
|
"type" => "INT",
|
|
"index" => "UNIQUE",
|
|
"nullable" => false,
|
|
"search"=>"invCode"
|
|
],
|
|
"authType" => [
|
|
"type" => "VARCHAR(255)",
|
|
"index" => "INDEX",
|
|
"nullable" => false
|
|
],
|
|
"authRefId" => [
|
|
"type" => "TEXT"
|
|
],
|
|
"fullName" => [
|
|
"type" => "VARCHAR(255)",
|
|
"nullable" => false,
|
|
"search"=>"like"
|
|
],
|
|
"companyId" => [
|
|
"type" => "INT",
|
|
"index" => "INDEX"
|
|
],
|
|
"eMail" => [
|
|
"type" => "VARCHAR(255)",
|
|
"index" => "INDEX",
|
|
"search"=>"like"
|
|
]
|
|
];
|
|
|
|
public function setPassword($passwd)
|
|
{
|
|
$this->__secret = xcrypt::hash($passwd);
|
|
}
|
|
|
|
public function checkPassword($passwd)
|
|
{
|
|
if (empty($this->__secret)) {
|
|
return false;
|
|
}
|
|
return ($this->__secret == xcrypt::hash($passwd));
|
|
}
|
|
|
|
protected function validateSave()
|
|
{
|
|
if ($this->invCode->isNull()) {
|
|
$this->invCode->issue("core", get_class($this));
|
|
}
|
|
|
|
if (empty($this->login)) {
|
|
$this->login=(string)$this->invCode;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function __xConstruct()
|
|
{
|
|
$this->invCode = new UID();
|
|
}
|
|
|
|
public function getAccessRules()
|
|
{
|
|
$cache = new cache();
|
|
$ACLS = $cache->get("UserACL." . $this->id, true);
|
|
if ($ACLS !== null) {
|
|
return $ACLS;
|
|
}
|
|
|
|
$rv = [];
|
|
// merge all ACLs
|
|
foreach (userGroup::getForUser($this, false, $this->sql) as $val) {
|
|
$rv = array_merge_recursive($rv, $val->accessRules);
|
|
}
|
|
|
|
// deduplicate
|
|
foreach ($rv as &$val) {
|
|
$val = array_unique($val);
|
|
}
|
|
$cache->set("UserACL." . $this->id, $rv);
|
|
return $rv;
|
|
}
|
|
|
|
public function checkAccess(string $rule, string $modInstance)
|
|
{
|
|
$ACLS = $this->getAccessRules();
|
|
|
|
if ($rule=="allUsers") {
|
|
return true;
|
|
}
|
|
|
|
|
|
if (array_key_exists($modInstance, $ACLS) && (array_search($rule, $ACLS[$modInstance]) !== false)) {
|
|
return true;
|
|
}
|
|
|
|
if (array_key_exists("<all>", $ACLS) && (array_search($rule, $ACLS["<all>"]) !== false)) {
|
|
return true;
|
|
}
|
|
|
|
if (array_key_exists("<all>", $ACLS) && (array_search("isRoot", $ACLS["<all>"]) !== false)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function flushACRCache()
|
|
{
|
|
$cache = new cache();
|
|
$cache->set("UserACL." . $this->id, null);
|
|
}
|
|
|
|
public function __get($key)
|
|
{
|
|
switch ($key) {
|
|
case "active":
|
|
return $this->active && ! $this->deleted;
|
|
|
|
default:
|
|
return parent::__get($key);
|
|
}
|
|
}
|
|
|
|
public static function getByRefID($authMethod,$userRefId) {
|
|
$ref=new static();
|
|
$sql = $ref->getSql();
|
|
$res = $sql->quickExec1Line($ref->sqlSelectTemplate. " where `authType`='".$authMethod."' and `authRefId`='".common::clearInput($userRefId)."'");
|
|
if ($res) {
|
|
return new static($res);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static function getByEmail($eMail) {
|
|
$ref=new static();
|
|
$sql = $ref->getSql();
|
|
$res = $sql->quickExec1Line($ref->sqlSelectTemplate. " where `eMail`='".common::clearInput($eMail,"@0-9A-Za-z._-")."' and `deleted` != 1");
|
|
if ($res) {
|
|
return new static($res);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static function getByLogin($login) {
|
|
$ref=new static();
|
|
$sql = $ref->getSql();
|
|
$res = $sql->quickExec1Line($ref->sqlSelectTemplate. " where `login`='".common::clearInput($login)."'");
|
|
if ($res) {
|
|
return new static($res);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected function prepareCodeForConfirmation($operation) {
|
|
$confCode=new confirmCode();
|
|
$confCode->class=__CLASS__;
|
|
$confCode->instance="core";
|
|
$confCode->reference=$this->id;
|
|
$confCode->operation=$operation;
|
|
$confCode->payload=["eMailAddress"=>$this->eMail];
|
|
return $confCode;
|
|
}
|
|
|
|
public function sendEMailConfirmation() {
|
|
if ($this->eMailConfirmed) {
|
|
foxException::throw("WARN", "Already confirmed", 406,"ARCF");
|
|
}
|
|
|
|
if (!common::validateEMail($this->eMail)) {
|
|
foxException::throw("ERR", "Invalid address format", 406,"WREML");
|
|
}
|
|
|
|
$confCode=$this->prepareCodeForConfirmation("eMailConfirmation");
|
|
$confCode->save();
|
|
|
|
$m=new mailMessage();
|
|
$m->addRecipient($this->eMail);
|
|
$m->subject=langPack::getAndReplace("core.eMailConfirmMessageTitle");
|
|
$m->bodyHTML=langPack::getAndReplace("core.eMailConfirmMessage",["confCodePrint"=>$confCode->code]);
|
|
$m->send();
|
|
}
|
|
|
|
public function validateEMailConfirmation($code) {
|
|
$confCode=$this->prepareCodeForConfirmation("eMailConfirmation");
|
|
if (!$confCode->fillByHash()) {
|
|
foxException::throw("ERR","Invalid code",406,"IVCC");
|
|
}
|
|
if ($confCode->code != $code) {
|
|
return false;
|
|
}
|
|
|
|
if ($confCode->expireStamp->stamp<time()) {
|
|
return false;
|
|
}
|
|
|
|
$this->eMailConfirmed=true;
|
|
$this->save();
|
|
$confCode->delete();
|
|
return true;
|
|
}
|
|
|
|
public function sendPasswordRecovery() {
|
|
if ($this->authType!=='internal' || !$this->eMailConfirmed) { throw new foxException("Not acceptable",406);}
|
|
$confCode=$this->prepareCodeForConfirmation("passwordRecovery");
|
|
$confCode->save();
|
|
|
|
$m=new mailMessage();
|
|
$m->addRecipient($this);
|
|
$m->subject=langPack::getAndReplace("core.accessRecoverMessageTitle");
|
|
$m->bodyHTML=langPack::getAndReplace("core.accessRecoverMessage",["confCodePrint"=>$confCode->code, "eMailEncoded"=>$this->eMail]);
|
|
$m->send();
|
|
}
|
|
|
|
public function validateRecoveryCode($code,$delete=false) {
|
|
if ($this->authType!=='internal' || !$this->eMailConfirmed) { throw new foxException("Not acceptable",406);}
|
|
$confCode=$this->prepareCodeForConfirmation("passwordRecovery");
|
|
$confCode->fillByHash();
|
|
if ($confCode->code != $code) {
|
|
return false;
|
|
}
|
|
|
|
if ($confCode->expireStamp->stamp<time()) {
|
|
return false;
|
|
}
|
|
|
|
if ($delete) { $confCode->delete();}
|
|
return true;
|
|
}
|
|
|
|
public function export() {
|
|
$rv=parent::export();
|
|
$rv["config"]=(object)$this->config;
|
|
return $rv;
|
|
}
|
|
/**
|
|
* @param array $options - ["groups" - array of userGroup, if set - search will performed only in it]
|
|
*/
|
|
protected static function xSearch($where, $pattern, ?array $options, sql $sql) {
|
|
$ruleJoin=null;
|
|
|
|
if ($options["groups"]) {
|
|
$groups="";
|
|
foreach ($options["groups"] as $group) {
|
|
$groups .= (empty($groups)?"":",")."\"".$group->id."\"";
|
|
}
|
|
$ruleJoin = " INNER JOIN `tblUserGroupLink` as `l` on `l`.`userId`=`i`.`id` AND `l`.`groupId` in ($groups)";
|
|
}
|
|
|
|
return ["where"=>$where, "join"=>$ruleJoin, "group"=>"`i`.`id`"];
|
|
}
|
|
|
|
### REST API
|
|
public static function API_GET_list(request $request)
|
|
{
|
|
if (! $request->user->checkAccess("adminUsers", "core")) {
|
|
throw new foxException("Forbidden", 403);
|
|
}
|
|
return static::search()->result;
|
|
}
|
|
|
|
public static function API_POST_search(request $request) {
|
|
|
|
$opts=[];
|
|
if ($request->checkAccess("viewAllUsers") || $request->checkAccess("adminUsers")) {
|
|
$opts=[];
|
|
} else if ($request->checkAccess("viewOwnListsUsers")) {
|
|
$opts = [
|
|
"groups"=>userGroup::getForUser($request->user,true),
|
|
];
|
|
} else {
|
|
$rv=new searchResult();
|
|
$rv->push($request->user);
|
|
return $rv;
|
|
|
|
}
|
|
|
|
return static::search(
|
|
$request->getRequestBodyItem("pattern"),
|
|
$request->getRequestBodyItem("pageSize"),
|
|
$request->getRequestBodyItem("page"),
|
|
$opts
|
|
);
|
|
|
|
}
|
|
|
|
public static function APIX_GET_sendEMailConfirmation(request $request) {
|
|
if (! $request->user->checkAccess("adminUsers", "core")) {
|
|
throw new foxException("Forbidden", 403);
|
|
}
|
|
$user = new static(common::clearInput($request->function,"0-9"));
|
|
$user->sendEMailConfirmation();
|
|
static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$user->login,$request->user,"user",$user->id,null,logEntry::sevInfo);
|
|
}
|
|
|
|
public static function API_GET_sendEMailConfirmation(request $request) {
|
|
$request->user->sendEMailConfirmation();
|
|
static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$request->user->login,$request->user,"user",$request->user->id,null,logEntry::sevInfo);
|
|
}
|
|
|
|
public static function API_POST_validateEMailCode(request $request) {
|
|
if ($request->user->validateEMailConfirmation(common::clearInput($request->requestBody->code,"0-9"))) {
|
|
static::log($request->instance,__FUNCTION__, "Mail address confirmed for user ".$request->user->login,$request->user,"user",$request->user->id,null,logEntry::sevInfo);
|
|
return;
|
|
} else {
|
|
foxException::throw("ERR", "Validation failed", 400,"IVCC");
|
|
}
|
|
}
|
|
|
|
public static function API_PATCH(request $request) {
|
|
if (!empty($request->parameters)) { throw new foxException("Invalid request", 400); }
|
|
if ($request->user->id == $request->function) {
|
|
$u=$request->user;
|
|
} else {
|
|
$request->blockIfNoAccess("adminUsers", "core");
|
|
$u=new static(common::clearInput($request->function));
|
|
}
|
|
|
|
$resendEMailConfirmation=false;
|
|
if (property_exists($request->requestBody, "fullName")) { $u->fullName=$request->requestBody->fullName; }
|
|
if (property_exists($request->requestBody, "enabled")) { $u->active=$request->requestBody->enabled==1; }
|
|
if (property_exists($request->requestBody, "eMail") && $request->requestBody->eMail != $u->eMail) {
|
|
if ($request->requestBody->eMail && !common::validateEMail($request->requestBody->eMail)) {
|
|
foxException::throw(foxException::STATUS_ERR, "Invalid email format", 400, "WREML");
|
|
}
|
|
|
|
if ($request->requestBody->eMail) {
|
|
if ($rx=static::getByEmail($request->requestBody->eMail)) {
|
|
trigger_error(json_encode($rx));
|
|
foxException::throw(foxException::STATUS_ERR, "EMail already in-use", 400, "UAX");
|
|
}
|
|
$u->eMail=$request->requestBody->eMail;
|
|
$u->eMailConfirmed=false;
|
|
$resendEMailConfirmation=true;
|
|
} else {
|
|
$u->eMail=null;
|
|
}
|
|
}
|
|
|
|
if (!empty($request->requestBody->password)) {
|
|
if (strlen($request->requestBody->password) < 6) {
|
|
throw new foxException("Password too short");
|
|
}
|
|
|
|
$u->setPassword($request->requestBody->password);
|
|
}
|
|
$u->save();
|
|
if ($resendEMailConfirmation) {
|
|
$u->sendEMailConfirmation();
|
|
}
|
|
return $u;
|
|
}
|
|
|
|
public static function APIX_PATCH_settings(request $request) {
|
|
if ($request->user->id == $request->function) {
|
|
$u=$request->user;
|
|
} else {
|
|
$request->blockIfNoAccess("adminUsers", "core");
|
|
$u=new static(common::clearInput($request->function));
|
|
}
|
|
|
|
foreach ($request->requestBody as $key=>$val) {
|
|
$u->config[common::clearInput($key)]=$val;
|
|
}
|
|
$u->save();
|
|
return $u->config;
|
|
}
|
|
|
|
public static function API_DELETE(request $request) {
|
|
$request->blockIfNoAccess("adminUsers", "core");
|
|
$u=new static(common::clearInput($request->function));
|
|
$u->delete();
|
|
}
|
|
}
|
|
|
|
?>
|