mirror of https://github.com/openvk/chandler.git
183 lines
5.2 KiB
PHP
183 lines
5.2 KiB
PHP
<?php declare(strict_types=1);
|
|
namespace Chandler\Security;
|
|
use Chandler\Database\DatabaseConnection;
|
|
use Chandler\Security\Authorization\Permissions;
|
|
use Chandler\Security\Authorization\PermissionBuilder;
|
|
use Nette\Database\Table\ActiveRow;
|
|
use Nette\Database\UniqueConstraintViolationException;
|
|
|
|
/**
|
|
* User class.
|
|
*
|
|
* @author kurotsun <celestine@vriska.ru>
|
|
*/
|
|
class User
|
|
{
|
|
/**
|
|
* @var \Nette\Database\Context DB Explorer
|
|
*/
|
|
private $db;
|
|
|
|
/**
|
|
* @var \Nette\Database\Table\ActiveRow ActiveRow that represents user
|
|
*/
|
|
private $user;
|
|
/**
|
|
* @var bool Does this user is not the one who is logged in, but substituted?
|
|
*/
|
|
private $tainted;
|
|
|
|
/**
|
|
* @param \Nette\Database\Table\ActiveRow $user ActiveRow that represents user
|
|
* @param bool $tainted Does this user is not the one who is logged in, but substituted?
|
|
*/
|
|
function __construct(ActiveRow $user, bool $tainted = false)
|
|
{
|
|
$this->db = DatabaseConnection::i()->getContext();
|
|
$this->user = $user;
|
|
$this->tainted = $tainted;
|
|
}
|
|
|
|
/**
|
|
* Computes hash for a password.
|
|
*
|
|
* @param string $password password
|
|
* @return string hash
|
|
*/
|
|
private function makeHash(string $password): string
|
|
{
|
|
$salt = openssl_random_pseudo_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
|
|
$hash = sodium_crypto_pwhash(
|
|
16,
|
|
$password,
|
|
$salt,
|
|
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
|
|
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
|
|
SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13
|
|
);
|
|
|
|
return bin2hex($hash) . "$" . bin2hex($salt);
|
|
}
|
|
|
|
/**
|
|
* Get user's GUID.
|
|
*
|
|
* @return string GUID
|
|
*/
|
|
function getId(): string
|
|
{
|
|
return $this->user->id;
|
|
}
|
|
|
|
/**
|
|
* Get user's DB data as an array.
|
|
*
|
|
* @return array DB data in form of associative array
|
|
*/
|
|
function getAttributes(): array
|
|
{
|
|
return (array) $this->user;
|
|
}
|
|
|
|
/**
|
|
* Get Permission Manager object.
|
|
*
|
|
* @api
|
|
* @see \Chandler\Security\User::can
|
|
* @return \Chandler\Security\Authorization\Permissions Permission Manager
|
|
*/
|
|
function getPermissions(): Permissions
|
|
{
|
|
return new Permissions($this);
|
|
}
|
|
|
|
/**
|
|
* Get ActiveRow that represents user
|
|
*
|
|
* @return \Nette\Database\Table\ActiveRow ActiveRow
|
|
*/
|
|
function getRaw(): ActiveRow
|
|
{
|
|
return $this->user;
|
|
}
|
|
|
|
/**
|
|
* Checks if this user is not the one who is logged in, but substituted
|
|
*
|
|
* @return bool Does this user is not the one who is logged in, but substituted?
|
|
*/
|
|
function isTainted(): bool
|
|
{
|
|
return $this->tainted;
|
|
}
|
|
|
|
/**
|
|
* Begins to build permission for checking it's status using Permission Builder.
|
|
* To get permission status you should chain methods like this:
|
|
* $user->can('do something')->model('\app\Web\Models\MyModel')->whichBelongsTo(10);
|
|
* In this case whichBelongsTo will automatically build permission and check if user
|
|
* has it. If you need to build permission for something another use {@see \Chandler\Security\User::getPermissions}.
|
|
*
|
|
* @api
|
|
* @uses \Chandler\Security\Authorization\PermissionBuilder::can
|
|
* @return \Chandler\Security\Authorization\Permissions Permission Manager
|
|
*/
|
|
function can(string $action): PermissionBuilder
|
|
{
|
|
$pb = new PermissionBuilder($this->getPermissions());
|
|
|
|
return $pb->can($action);
|
|
}
|
|
|
|
/**
|
|
* Updates user password.
|
|
* If $oldPassword parameter is passed it will update password only if current
|
|
* user password (not the new one) matches $oldPassword.
|
|
*
|
|
* @api
|
|
* @param string $password New Password
|
|
* @param string|null $oldPassword Current Password
|
|
* @return bool False if token manipulation error has been thrown
|
|
*/
|
|
function updatePassword(string $password, ?string $oldPassword = NULL): bool
|
|
{
|
|
if(!is_null($oldPassword))
|
|
if(!Authenticator::verifyHash($oldPassword, $this->getRaw()->passwordHash))
|
|
return false;
|
|
|
|
$users = DatabaseConnection::i()->getContext()->table("ChandlerUsers");
|
|
$users->where("id", $this->getId())->update([
|
|
"passwordHash" => $this->makeHash($password),
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates new user if login has not been taken yet.
|
|
*
|
|
* @api
|
|
* @param string $login Login (usually an email)
|
|
* @param string $password Password
|
|
* @return self|null New user if successful, null otherwise
|
|
*/
|
|
static function create(string $login, string $password): ?User
|
|
{
|
|
$users = DatabaseConnection::i()->getContext()->table("ChandlerUsers");
|
|
$hash = self::makeHash($password);
|
|
|
|
try {
|
|
$users->insert([
|
|
"login" => $login,
|
|
"passwordHash" => $hash,
|
|
]);
|
|
|
|
$user = $users->where("login", $login)->fetch();
|
|
} catch(UniqueConstraintViolationException $ex) {
|
|
return null;
|
|
}
|
|
|
|
return new static($user);
|
|
}
|
|
}
|