<?php /** * Personal info * @property $LastName string * @property $FirstName string * @property $SecondName string * @property Model_Faculty $Faculty * @property $FacultyName string * @property $FacultyAbbr string * * Account info * @property $ID int * @property $Login string * @property $EMail string * @property $Type string teacher / student * @property $Role string description * @property $RoleMark int * @property $IsEnabled bool * @property $Code * @property $UserAgent * * Session * @property $SemesterID int * @property-read $last_active int * @property-read $LoggedIn bool * @property-read $UserHash string * @property-read $PasswordHash string * @property-read $start_time int * @property $RecordBookID int */ class User implements ArrayAccess { const RIGHTS_AUTHORIZED = -2; // no guests const RIGHTS_ANYBODY = 1; // guests too const RIGHTS_STUDENT = 2; const RIGHTS_TEACHER = 4; const RIGHTS_ADMIN = 8; const RIGHTS_DEAN = 16; /** @var Session */ protected $_session; /** @var Kohana_Config_Group */ protected $_config; protected $_general; /** @var bool responsible for renewal of session lifetime (see instance() ) */ protected static $_updateSession; /** @var self */ protected static $_instance; /** * todo: mark as deprecated cause singleton is an anti-pattern! * @param bool $updateSession set false, if session lifetime shouldn't be renewed * @return self class instance (singleton-pattern) */ public static function instance($updateSession = true) { self::$_updateSession = $updateSession; if (!isset(self::$_instance)) { $config = Kohana::$config->load('account'); self::$_instance = new self($config); } return self::$_instance; } private function __construct($config = []) { $this->_config = $config; $this->_general = Model_System::loadConfig("general.json"); $this->_config['hash_key'] = $this->_general->HashKey; $this->_config['hash_method'] = 'sha256'; $session = $this->_session = Session::instance(); if (!isset($session['RoleMark'])) $session['RoleMark'] = 1; if (self::$_updateSession) { $session->regenerate(); $session->set('start_time', time()); } } /** * @param $mask * @throws LogicException */ public function checkAccess($mask) { $goodBoy = $this->RoleMark & $mask; if (!$goodBoy) throw new LogicException(Error::ACCESS_DENIED); } public function isDean() { return (bool) ($this->RoleMark & self::RIGHTS_DEAN); } public function isAdmin() { return (bool) ($this->RoleMark & self::RIGHTS_ADMIN); } public function isTeacher() { return (bool) ($this->RoleMark & self::RIGHTS_TEACHER); } public function isStudent() { return (bool) ($this->RoleMark & self::RIGHTS_STUDENT); } public function isAuthorized() { return (bool) ($this->RoleMark & ~self::RIGHTS_ANYBODY); } /** * Регистрирует нового пользователя и осуществляет вход. * Проверяет корректность кода активации и существование * аккаунтов с такими же авторизационными данными. * * @param string $code Код активации * @param string $email E-Mail адрес * @param string $login * @param string $password * @return string|bool текст ошибки, иначе false */ public function signUp($code, $email, $login, $password) { $id = Model_Account::activateAccount($login, $password, $email, $code); switch ($id) { case -1: return 'something went wrong'; case -2: return 'invalid activation code'; case -3: return 'mail already exists'; case -4: return 'login already exists'; } $this->initSession($id, $this->hash($password)); return false; } /** * Проверяет корректность авторизационных данных. * * @param string $login * @param string $password * @return bool true, если авторизация прошла успешно, * и false, если данные являются некорректными. */ public function signIn($login, $password) { $id = (int) Model_Account::checkAuth($login, $password); return $this->initSession($id, $this->hash($password)); } /** * Проверяет существования пользователя с заданным globalKey и авторизует его * * @param string $globalKey * @return bool true, если авторизация прошла успешно, * и false, если данные являются некорректными. */ public function signInByOpenID($globalKey) { $id = (int) Model_Account::checkAuthOpenID($globalKey); return $this->initSession($id, $this->hash($globalKey)); } public function signInByToken($token) { $id = (int) Model_Account::checkAuthToken($token); return $this->initSession($id, $this->hash($token)); } protected function initSession($id, $passHash) { if ($id <= 0) return false; $source = $id . Request::$user_agent . Request::$client_ip; $userHash = $this->hash($source) . $this->_config['hash_key']; $passwordHash = $this->hash($passHash . $this->_config['hash_key']); Cookie::set('userhash', $passwordHash); $userInfo = Model_Account::with($id, $this->_general->SemesterID); $session = $this->_session; $session->regenerate(); $session->set('ID', $id); $session->set('SemesterID', $this->_general->SemesterID); $session->set('LoggedIn', true); $session->set('UserHash', $this->hash($userHash)); $session->set('PasswordHash', $passwordHash); $session->set('start_time', time()); $session->set('RecordBookID', null); foreach ($userInfo as $key => $value) $session->set($key, $value); return true; } /** * Проверяет авторизационный статус пользователя и, если * пользователь имеет UserAgent и IP, отличные от хранимых * в сессии, осуществляет выход из текущего сеанса. * * @return bool true, если пользователь авторизован */ public function isSignedIn() { if ($this->_session->get('LoggedIn') && !$this->checkHash()) { $this->completeSignOut(); } return $this->_session->get('LoggedIn'); } protected function checkHash() { $id = $this->_session->get('ID'); $source = $id . Request::$user_agent . Request::$client_ip; $userHash = $this->hash($source) . $this->_config['hash_key']; $userCheck = $this->_session->get('UserHash') == $this->hash($userHash); $passCheck = Cookie::get('userhash') == $this->_session->get('PasswordHash'); return $userCheck AND $passCheck; } /** * Завершает текущий сеанс пользователя. * @return bool */ public function signOut() { if ($this->isSignedIn()) { return $this->completeSignOut(); } return false; } protected function completeSignOut() { $this->_session ->set('ID', false) ->set('LoggedIn', false) ->set('UserHash', false); Cookie::delete('userhash'); $this->_session->restart(); return true; } /** * Проверяет корректность данного пароля для текущего пользователя. * * @param string $password * @return bool */ public function checkPassword($password) { if (!$this->isSignedIn()) return false; $passHash = $this->hash($password); $computed = $this->hash($passHash . $this->_config['hash_key']); return $computed === $this->_session->get('PasswordHash'); } // todo: move to Model_Account public function changePassword($old, $new) { if (!$this->checkPassword($old)) return false; if (Model_Account::changePassword($this->ID, $new)) { $passHash = $this->hash($this->hash($new) . $this->_config['hash_key']); $this->_session->set('PasswordHash', $passHash); Cookie::set('userhash', $passHash); return true; } return false; } # todo: move to account public function changeProfile($data) { $this->checkAccess(User::RIGHTS_TEACHER); $res = Model_Teacher::with($this['TeacherID'])->changeInfo( $data['lastName'], $data['firstName'], $data['secondName'], $data['jobPositionID'], $data['departmentID'] ); if ($res) { $this->LastName = $data['lastName']; $this->FirstName = $data['firstName']; $this->SecondName = $data['secondName']; } } /* Info */ /** * Возвращает массив, содержащий пользовательские данные. * * @return array */ public function toArray() { if ($this->isSignedIn()) { return $this->_session->as_array(); } else { return []; } } /* Fields access */ function __set($name, $value) { $this->offsetSet($name, $value); } function __get($name) { # todo: move to $_SESSION if ($name == 'Faculty') return Model_Faculty::with($this->_session['FacultyID']); return $this->offsetGet($name); } public function offsetSet($offset, $value) { $this->_session[$offset] = $value; } public function offsetGet($offset) { if (isset($offset, $this->_session)) return $this->_session[$offset]; throw new ErrorException('No such field'); } public function offsetUnset($offset) { unset($this->_session[$offset]); } public function offsetExists($offset) { return isset($this->_session[$offset]); } /** * Perform a hmac hash, using the configured method. * * @param string $str string to hash * @return string */ protected function hash($str) { if (!$this->_config['hash_key']) { $this->_config['hash_key'] = $key = md5(time() . Request::$client_ip); # TODO: implement Model_Account::setHashKey($key); } return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']); } }