*/ 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); } }