chandler/chandler/Security/User.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);
}
}