diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/acl.php | 111 | ||||
-rw-r--r-- | models/action.php | 11 | ||||
-rw-r--r-- | models/base.php | 1 | ||||
-rw-r--r-- | models/last_login.php | 93 | ||||
-rw-r--r-- | models/remember_me.php | 336 | ||||
-rw-r--r-- | models/user.php | 69 |
6 files changed, 609 insertions, 12 deletions
diff --git a/models/acl.php b/models/acl.php index ea7dd5cb..c8a39ee4 100644 --- a/models/acl.php +++ b/models/acl.php @@ -4,16 +4,32 @@ namespace Model; require_once __DIR__.'/base.php'; +/** + * Acl model + * + * @package model + * @author Frederic Guillot + */ class Acl extends Base { - // Controllers and actions allowed from outside + /** + * Controllers and actions allowed from outside + * + * @access private + * @var array + */ private $public_actions = array( 'user' => array('login', 'check'), 'task' => array('add'), 'board' => array('readonly'), ); - // Controllers and actions allowed for regular users + /** + * Controllers and actions allowed for regular users + * + * @access private + * @var array + */ private $user_actions = array( 'app' => array('index'), 'board' => array('index', 'show', 'assign', 'assigntask', 'save'), @@ -21,10 +37,18 @@ class Acl extends Base 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'description', 'duplicate'), 'comment' => array('save', 'confirm', 'remove', 'update', 'edit'), 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index'), - 'config' => array('index'), + 'config' => array('index', 'removeremembermetoken'), ); - // Return true if the specified controller/action is allowed according to the given acl + /** + * Return true if the specified controller/action is allowed according to the given acl + * + * @access public + * @param array $acl Acl list + * @param string $controller Controller name + * @param string $action Action name + * @return bool + */ public function isAllowedAction(array $acl, $controller, $action) { if (isset($acl[$controller])) { @@ -34,37 +58,100 @@ class Acl extends Base return false; } - // Return true if the given action is public + /** + * Return true if the given action is public + * + * @access public + * @param string $controller Controller name + * @param string $action Action name + * @return bool + */ public function isPublicAction($controller, $action) { return $this->isAllowedAction($this->public_actions, $controller, $action); } - // Return true if the given action is allowed for a regular user + /** + * Return true if the given action is allowed for a regular user + * + * @access public + * @param string $controller Controller name + * @param string $action Action name + * @return bool + */ public function isUserAction($controller, $action) { return $this->isAllowedAction($this->user_actions, $controller, $action); } - // Return true if the logged user is admin + /** + * Return true if the logged user is admin + * + * @access public + * @return bool + */ public function isAdminUser() { - return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === '1'; + return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === true; } - // Return true if the logged user is not admin + /** + * Return true if the logged user is not admin + * + * @access public + * @return bool + */ public function isRegularUser() { - return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === '0'; + return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === false; } - // Get the connected user id + /** + * Get the connected user id + * + * @access public + * @return bool + */ public function getUserId() { return isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : 0; } - // Check if an action is allowed for the logged user + /** + * Check is the user is connected + * + * @access public + * @return bool + */ + public function isLogged() + { + return ! empty($_SESSION['user']); + } + + /** + * Check is the user was authenticated with the RememberMe or set the value + * + * @access public + * @param bool $value Set true if the user use the RememberMe + * @return bool + */ + public function isRememberMe($value = null) + { + if ($value !== null) { + $_SESSION['is_remember_me'] = $value; + } + + return empty($_SESSION['is_remember_me']) ? false : $_SESSION['is_remember_me']; + } + + /** + * Check if an action is allowed for the logged user + * + * @access public + * @param string $controller Controller name + * @param string $action Action name + * @return bool + */ public function isPageAccessAllowed($controller, $action) { return $this->isPublicAction($controller, $action) || diff --git a/models/action.php b/models/action.php index cc8f5cad..a0236eff 100644 --- a/models/action.php +++ b/models/action.php @@ -16,7 +16,18 @@ use \SimpleValidator\Validators; */ class Action extends Base { + /** + * SQL table name for actions + * + * @var string + */ const TABLE = 'actions'; + + /** + * SQL table name for action parameters + * + * @var string + */ const TABLE_PARAMS = 'action_has_params'; /** diff --git a/models/base.php b/models/base.php index 9b5dc67f..70a24321 100644 --- a/models/base.php +++ b/models/base.php @@ -54,6 +54,7 @@ abstract class Base /** * Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid() * + * @static * @access public * @return string Random token */ diff --git a/models/last_login.php b/models/last_login.php new file mode 100644 index 00000000..96cc6108 --- /dev/null +++ b/models/last_login.php @@ -0,0 +1,93 @@ +<?php + +namespace Model; + +require_once __DIR__.'/base.php'; + +/** + * LastLogin model + * + * @package model + * @author Frederic Guillot + */ +class LastLogin extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'last_logins'; + + /** + * Number of connections to keep for history + * + * @var integer + */ + const NB_LOGINS = 15; + + /** + * Authentication methods + * + * @var string + */ + const AUTH_DATABASE = 'database'; + const AUTH_REMEMBER_ME = 'remember_me'; + const AUTH_LDAP = 'ldap'; + const AUTH_GOOGLE = 'google'; + + /** + * Create a new record + * + * @access public + * @param string $auth_type Authentication method + * @param integer $user_id User id + * @param string $ip IP Address + * @param string $user_agent User Agent + * @return array + */ + public function create($auth_type, $user_id, $ip, $user_agent) + { + // Cleanup old sessions if necessary + $connections = $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('date_creation') + ->findAllByColumn('id'); + + if (count($connections) >= self::NB_LOGINS) { + + $this->db->table(self::TABLE) + ->eq('user_id', $user_id) + ->notin('id', array_slice($connections, 0, self::NB_LOGINS - 1)) + ->remove(); + } + + return $this->db + ->table(self::TABLE) + ->insert(array( + 'auth_type' => $auth_type, + 'user_id' => $user_id, + 'ip' => $ip, + 'user_agent' => $user_agent, + 'date_creation' => time(), + )); + } + + /** + * Get the last connections for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAll($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('date_creation') + ->columns('id', 'auth_type', 'ip', 'user_agent', 'date_creation') + ->findAll(); + } +} diff --git a/models/remember_me.php b/models/remember_me.php new file mode 100644 index 00000000..2454cc95 --- /dev/null +++ b/models/remember_me.php @@ -0,0 +1,336 @@ +<?php + +namespace Model; + +require_once __DIR__.'/base.php'; + +/** + * RememberMe model + * + * @package model + * @author Frederic Guillot + */ +class RememberMe extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'remember_me'; + + /** + * Cookie name + * + * @var string + */ + const COOKIE_NAME = '__R'; + + /** + * Expiration (60 days) + * + * @var integer + */ + const EXPIRATION = 5184000; + + /** + * Get a remember me record + * + * @access public + * @param integer $user_id User id + * @return mixed + */ + public function find($token, $sequence) + { + return $this->db + ->table(self::TABLE) + ->eq('token', $token) + ->eq('sequence', $sequence) + ->gt('expiration', time()) + ->findOne(); + } + + /** + * Get all sessions for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAll($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('date_creation') + ->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration') + ->findAll(); + } + + /** + * Authenticate the user with the cookie + * + * @access public + * @return bool + */ + public function authenticate() + { + $credentials = $this->readCookie(); + + if ($credentials !== false) { + + $record = $this->find($credentials['token'], $credentials['sequence']); + + if ($record) { + + // Update the sequence + $this->writeCookie( + $record['token'], + $this->update($record['token'], $record['sequence']), + $record['expiration'] + ); + + // Create the session + $user = new User($this->db, $this->event); + $acl = new Acl($this->db, $this->event); + + $user->updateSession($user->getById($record['user_id'])); + $acl->isRememberMe(true); + + return true; + } + } + + return false; + } + + /** + * Update the database and the cookie with a new sequence + * + * @access public + */ + public function refresh() + { + $credentials = $this->readCookie(); + + if ($credentials !== false) { + + $record = $this->find($credentials['token'], $credentials['sequence']); + + if ($record) { + + // Update the sequence + $this->writeCookie( + $record['token'], + $this->update($record['token'], $record['sequence']), + $record['expiration'] + ); + } + } + } + + /** + * Remove a session record + * + * @access public + * @param integer $session_id Session id + * @return mixed + */ + public function remove($session_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $session_id) + ->remove(); + } + + /** + * Remove the current RememberMe session and the cookie + * + * @access public + * @param integer $user_id User id + */ + public function destroy($user_id) + { + $credentials = $this->readCookie(); + + if ($credentials !== false) { + + $this->deleteCookie(); + + $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->eq('token', $credentials['token']) + ->remove(); + } + } + + /** + * Create a new RememberMe session + * + * @access public + * @param integer $user_id User id + * @param string $ip IP Address + * @param string $user_agent User Agent + * @return array + */ + public function create($user_id, $ip, $user_agent) + { + $token = hash('sha256', $user_id.$user_agent.$ip.$this->generateToken()); + $sequence = $this->generateToken(); + $expiration = time() + self::EXPIRATION; + + $this->cleanup($user_id); + + $this->db + ->table(self::TABLE) + ->insert(array( + 'user_id' => $user_id, + 'ip' => $ip, + 'user_agent' => $user_agent, + 'token' => $token, + 'sequence' => $sequence, + 'expiration' => $expiration, + 'date_creation' => time(), + )); + + return array( + 'token' => $token, + 'sequence' => $sequence, + 'expiration' => $expiration, + ); + } + + /** + * Remove old sessions for a given user + * + * @access public + * @param integer $user_id User id + * @return bool + */ + public function cleanup($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->lt('expiration', time()) + ->remove(); + } + + /** + * Return a new sequence token and update the database + * + * @access public + * @param string $token Session token + * @param string $sequence Sequence token + * @return string + */ + public function update($token, $sequence) + { + $new_sequence = $this->generateToken(); + + $this->db + ->table(self::TABLE) + ->eq('token', $token) + ->eq('sequence', $sequence) + ->update(array('sequence' => $new_sequence)); + + return $new_sequence; + } + + /** + * Encode the cookie + * + * @access public + * @param string $token Session token + * @param string $sequence Sequence token + * @return string + */ + public function encodeCookie($token, $sequence) + { + return implode('|', array($token, $sequence)); + } + + /** + * Decode the value of a cookie + * + * @access public + * @param string $value Raw cookie data + * @return array + */ + public function decodeCookie($value) + { + list($token, $sequence) = explode('|', $value); + + return array( + 'token' => $token, + 'sequence' => $sequence, + ); + } + + /** + * Return true if the current user has a RememberMe cookie + * + * @access public + * @return bool + */ + public function hasCookie() + { + return ! empty($_COOKIE[self::COOKIE_NAME]); + } + + /** + * Write and encode the cookie + * + * @access public + * @param string $token Session token + * @param string $sequence Sequence token + * @param string $expiration Cookie expiration + */ + public function writeCookie($token, $sequence, $expiration) + { + setcookie( + self::COOKIE_NAME, + $this->encodeCookie($token, $sequence), + $expiration, + BASE_URL_DIRECTORY, + null, + ! empty($_SERVER['HTTPS']), + true + ); + } + + /** + * Read and decode the cookie + * + * @access public + * @return mixed + */ + public function readCookie() + { + if (empty($_COOKIE[self::COOKIE_NAME])) { + return false; + } + + return $this->decodeCookie($_COOKIE[self::COOKIE_NAME]); + } + + /** + * Remove the cookie + * + * @access public + */ + public function deleteCookie() + { + setcookie( + self::COOKIE_NAME, + '', + time() - 3600, + BASE_URL_DIRECTORY, + null, + ! empty($_SERVER['HTTPS']), + true + ); + } +} diff --git a/models/user.php b/models/user.php index 496ae0da..5815b673 100644 --- a/models/user.php +++ b/models/user.php @@ -151,6 +151,10 @@ class User extends Base unset($user['password']); } + $user['id'] = (int) $user['id']; + $user['default_project_id'] = (int) $user['default_project_id']; + $user['is_admin'] = (bool) $user['is_admin']; + $_SESSION['user'] = $user; } @@ -274,7 +278,16 @@ class User extends Base $user = $this->getByUsername($values['username']); if ($user !== false && \password_verify($values['password'], $user['password'])) { + + // Create the user session $this->updateSession($user); + + // Setup the remember me feature + if (! empty($values['remember_me'])) { + $rememberMe = new RememberMe($this->db, $this->event); + $credentials = $rememberMe->create($user['id'], $this->getIpAddress(), $this->getUserAgent()); + $rememberMe->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']); + } } else { $result = false; @@ -287,4 +300,60 @@ class User extends Base $errors ); } + + /** + * Get the user agent of the connected user + * + * @access public + * @return string + */ + public function getUserAgent() + { + return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT']; + } + + /** + * Get the real IP address of the connected user + * + * @access public + * @param bool $only_public Return only public IP address + * @return string + */ + public function getIpAddress($only_public = false) + { + $keys = array( + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR' + ); + + foreach ($keys as $key) { + + if (isset($_SERVER[$key])) { + + foreach (explode(',', $_SERVER[$key]) as $ip_address) { + + $ip_address = trim($ip_address); + + if ($only_public) { + + // Return only public IP address + if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { + return $ip_address; + } + } + else { + + return $ip_address; + } + } + } + } + + return t('Unknown'); + } } |