<?php /** * Personal info * @property $LastName string * @property $FirstName string * @property $SecondName string * @property $FacultyID int * @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-read $SemesterID int * @property-read $last_active int * @property-read $LoggedIn bool * @property-read $UserHash string * @property-read $PasswordHash string * @property-read $start_time int */ class User implements ArrayAccess { protected static $_instance; protected $_session; protected $_config; protected $_userInfo; protected static $_flag; /** * todo: mark deprecated as singleton is an anti-pattern! * @return self class instance (singleton-pattern) */ public static function instance($state = false) { self::$_flag = $state; if (!isset(self::$_instance)) { $config = Kohana::$config->load('account'); self::$_instance = new self($config); } return self::$_instance; } private function __construct($config = array()) { $this->_config = $config; $this->_session = Session::instance(); $this->_userInfo['RoleMark'] = (int)1; $this->_config['hash_key'] = Model_Account::getHashKey(); $this->_config['hash_method'] = 'sha256'; $isSignedIn = $this->isSignedIn(); if ($isSignedIn) { $this->_userInfo = $this->_session->get('UserInfo'); if (self::$_flag != true) { $this->_session->regenerate(); $this->_session->set('start_time', time()); } } } const RIGHTS_ANYBODY = 1; const RIGHTS_STUDENT = 2; const RIGHTS_TEACHER = 4; const RIGHTS_ADMIN = 8; const RIGHTS_DEAN = 16; /** * @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_DEAN); } 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 array Пару вида <tt>(is_ok, err_msg)</tt> */ public function signUp($code, $email, $login, $password) { $account = Account::instance(); $isValid = Model_Account::isActivationCodeValid($code); if (!$isValid) { return array(false, 'invalid_code'); } $isLogin = $account->doesLoginExist($login); $isMail = $account->isMailValid($email); if ($isLogin) { return array(false, 'login_exists'); } else { if ($isMail) { return array(false, 'mail_exists'); } } $id = Model_Account::activateAccount($login, $password, $email, $code); $this->completeSignIn($id, $this->hash($password)); return array(true, 'ok'); } /** * Проверяет корректность авторизационных данных. * * @param string $login * @param string $password * @return bool true, если авторизация прошла успешно, * и false, если данные являются некорректными. */ public function signIn($login, $password) { $id = (int)Model_Account::checkAuth($login, $password); if ($id === -1) { return false; } else { return $this->completeSignIn($id, $this->hash($password)); } } protected function completeSignIn($id, $passHash) { $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::getUserInfo($id); $this->_session->set('UserInfo', $userInfo); $this->_session->regenerate(); $this->_session->set('ID', $id); $this->_session->set('LoggedIn', true); $this->_session->set('UserHash', $this->hash($userHash)); $this->_session->set('PasswordHash', $passwordHash); $this->_session->set('start_time', time()); $this->_session->set('SemesterID', $userInfo['SemesterID']); return true; } /** * Проверяет авторизационный статус пользователя и, если * пользователь имеет UserAgent и IP, отличные от хранимых * в сессии, осуществляет выход из текущего сеанса. * * @return bool true, если пользователь авторизован */ public function isSignedIn() { $session = &$this->_session; if ($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'); unset($this->_userInfo); $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'); } public function changePassword($old, $new) { if (!$this->checkPassword($old)) return false; Model_Account::changeAccountData($this->ID, $new, 'password'); $passhash = $this->hash($this->hash($new) . $this->_config['hash_key']); $this->_session->set('PasswordHash', $passhash); Cookie::set('userhash', $passhash); return true; } public function changeLogin($login) { if (!$this->isSignedIn() || Account::instance()->doesLoginExist($login)) return false; Model_Account::changeAccountData($this->ID, $login, 'login'); return true; } public function changeMail($email) { if (!$this->isSignedIn() || Account::instance()->isMailValid($email)) return false; $token = md5(time() . $this->EMail . $email); $this->_session->set('NewMail_Token', $token); $this->_session->set('NewMail_Adress', $email); return $token; } public function completeChangeMail($token) { $email = $this->_session->get('NewMail_Adress'); if ($token == $this->_session->get('NewMail_Token') AND !Account::instance()->isMailValid($email)) { Model_Account::changeAccountData($this->ID, $email, 'email'); return true; } else { return false; } } public function changeProfile($data) { if ($this->Type == 'teacher') { Model_Account::changeTeacherInfo($this['TeacherID'], $data['lastName'], $data['firstName'], $data['secondName'], $data['jobPositionID'], $data['departmentID']); } } /* Info */ /** * Возвращает массив, содержащий пользовательские данные. * * @return array */ public function toArray() { if ($this->isSignedIn()) { return $this->_userInfo + $this->_session->as_array(); // fixme: nobody knows what _session contains! } else { return array(); } } /* Fields access */ function __set($name, $value) { $this->offsetSet($name, $value); } function __get($name) { return $this->offsetGet($name); } public function offsetSet($offset, $value) { if ($this->_userInfo && array_key_exists($offset, $this->_userInfo)) { $this->_userInfo[$offset] = $value; } elseif (isset($offset, $this->_session)) { $this->_session[$offset] = $value; } else { // TODO: _userInfo may be null $this->_userInfo[$offset] = $value; } } public function offsetGet($offset) { if ($this->_userInfo && array_key_exists($offset, $this->_userInfo)) return $this->_userInfo[$offset]; else if (isset($offset, $this->_session)) return $this->_session[$offset]; throw new ErrorException('No such field'); } public function offsetUnset($offset) { if (array_key_exists($offset, $this->_userInfo)) unset($this->_userInfo[$offset]); else unset($this->_session[$offset]); } public function offsetExists($offset) { return array_key_exists($offset, $this->_userInfo) || 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); Model_Account::setHashKey($key); } return hash_hmac($this->_config['hash_method'], $str, $this->_config['hash_key']); } }