1
0
mirror of https://github.com/e107inc/e107.git synced 2025-08-24 15:13:04 +02:00

EONE-62 (New Feature): login/init session working now; added user preferences management; new experimental feature 'login as'; need more work regarding DB related operations and security

This commit is contained in:
secretr
2010-05-13 15:47:31 +00:00
parent 252100989e
commit f713eb0d15
7 changed files with 705 additions and 185 deletions

View File

@@ -69,7 +69,7 @@ class e_user_model extends e_front_model
protected $_validation_rules = array(
'user_name' => array('string', '1', 'LAN_USER_01', 'LAN_USER_HELP_01'), // TODO - regex
'user_loginname' => array('string', '1', 'LAN_USER_02', 'LAN_USER_HELP_02'), // TODO - regex
'user_password' => array('string', '5', 'LAN_USER_05', 'LAN_USER_HELP_05'), // TODO - pref - modify it somewhere below
'user_password' => array('compare', '5', 'LAN_USER_05', 'LAN_USER_HELP_05'), // TODO - pref - modify it somewhere below - prepare_rules()?
'user_email' => array('email', '', 'LAN_USER_08', 'LAN_USER_HELP_08'),
);
@@ -113,6 +113,12 @@ class e_user_model extends e_front_model
*/
protected $_extended_structure = null;
/**
* User preferences model
* @var e_user_pref
*/
protected $_user_config = null;
/**
* User model of current editor
* @var e_user_model
@@ -176,7 +182,7 @@ class e_user_model extends e_front_model
public function hasEditor()
{
return null !== $this->_editor;
return (null !== $this->_editor);
}
final protected function _setClassList($uid = '')
@@ -247,24 +253,74 @@ class e_user_model extends e_front_model
*
* @param string$field
* @param string $default
* @param boolean $short if true, 'user_' prefix will be added to field name
* @return mixed
*/
public function getValue($field, $default = '')
public function getValue($field, $default = '', $short = true)
{
$field = 'user_'.$field;
if($short) $field = 'user_'.$field;
return $this->get($field, $default);
}
/**
* Set User value
* Set User value - only when writable
* @param string $field
* @param mixed $value
* @param boolean $short if true, 'user_' prefix will be added to field name
* @return e_user_model
*/
public function setValue($field, $value, $short = true)
{
if($short) $field = 'user_'.$field;
if($this->isWritable($field)) $this->set($field, $value, true);
return $this;
}
/**
* Get user preference
* @param string $pref_name
* @param mixed $default
* @return mixed
*/
public function getPref($pref_name = null, $default = null)
{
if(null === $pref_name) return $this->getConfig()->getData();
return $this->getConfig()->get($pref_name, $default);
}
/**
* Set user preference
* @param string $pref_name
* @param mixed $value
* @return e_user_model
*/
public function setValue($field, $value)
public function setPref($pref_name, $value = null)
{
$field = 'user_'.$field;
$this->set($field, $value, true);
$this->getConfig()->set($pref_name, $value);
return $this;
}
/**
* Get user preference (advanced - slower)
* @param string $pref_path
* @param mixed $default
* @param integer $index if number, value will be exploded by "\n" and corresponding index will be returned
* @return mixed
*/
public function findPref($pref_path = null, $default = null, $index = null)
{
return $this->getConfig()->getData($pref_path, $default, $index);
}
/**
* Set user preference (advanced - slower)
* @param string $pref_path
* @param mixed $value
* @return e_user_model
*/
public function setPrefData($pref_path, $value = null)
{
$this->getConfig()->setData($pref_path, $value = null);
return $this;
}
@@ -319,6 +375,32 @@ class e_user_model extends e_front_model
return $this;
}
/**
* Get user config model
*
* @return e_user_pref
*/
public function getConfig()
{
if (null === $this->_user_config)
{
$this->_user_config = new e_user_pref($this);
}
return $this->_user_config;
}
/**
* Set user config model
*
* @param e_user_pref $user_config
* @return e_user_model
*/
public function setConfig(e_user_pref $user_config)
{
$this->_user_config = $user_config;
return $this;
}
/**
* Get current user editor model
* @return e_user_model
@@ -345,9 +427,11 @@ class e_user_model extends e_front_model
*/
public function isWritable($field)
{
if (!is_string($field))
return true;
return !in_array($field, array($this->getFieldIdName(), 'user_admin', 'user_perms'));
$perm = false;
$editor = $this->getEditor();
if($this->getId() === $editor->getId() || $editor->isMainAdmin() || $editor->checkAdminPerms('4'))
$perm = true;
return ($perm && !in_array($field, array($this->getFieldIdName(), 'user_admin', 'user_perms', 'user_prefs')));
}
/**
@@ -357,7 +441,7 @@ class e_user_model extends e_front_model
*/
protected function setAsTarget()
{
e107::setRegistry('targets/core/user/'.$this->getId(), $this);
e107::setRegistry('core/e107/user/'.$this->getId(), $this);
return $this;
}
@@ -368,7 +452,7 @@ class e_user_model extends e_front_model
*/
protected function clearTarget()
{
e107::setRegistry('targets/core/user'.$this->getId(), null);
e107::setRegistry('core/e107/user'.$this->getId(), null);
return $this;
}
@@ -386,6 +470,39 @@ class e_user_model extends e_front_model
}
}
/**
* Additional security while applying posted
* data to user model
* @return e_user_model
*/
public function mergePostedData()
{
$posted = $this->getPostedData();
foreach ($posted as $key => $value)
{
if(!$this->isWritable($key))
{
$this->removePosted($key);
continue;
}
$this->_modifyPostedData($key, $value);
}
parent::mergePostedData(true, true, true);
return $this;
}
protected function _modifyPostedData($key, $value)
{
// TODO - add more here
switch ($key)
{
case 'password1':
// compare validation rule
$this->setPosted('user_password', array($value, $this->getPosted('password2')));
break;
}
}
/**
* Send model data to DB
*/
@@ -396,7 +513,10 @@ class e_user_model extends e_front_model
return false; // TODO - message, admin log
}
// TODO - do the save manual in this order: validate() on user model, save() on extended fields, save() on user model
// sync user prefs
$this->getConfig()->apply();
// TODO - do the save manually in this order: validate() on user model, save() on extended fields, save() on user model
$ret = parent::save(true, $force, $session);
if(false !== $ret && null !== $this->_extended_model) // don't load extended fields if not already used
{
@@ -468,15 +588,27 @@ class e_system_user extends e_user_model
}
/**
* Current system user - additional data protection is required
* Current system user
* @author SecretR
*/
class e_user extends e_user_model
{
private $_session_data = null;
private $_session_key = null;
private $_session_type = null;
private $_session_error = false;
private $_parent_id = false;
private $_parent_data = array();
private $_parent_extmodel = null;
private $_parent_extstruct = null;
private $_parent_config = null;
public function __construct()
{
// reference to self
$this->load()->setEditor($this);
$this->setSessionData() // retrieve data from current session
->load() // load current user from DB
->setEditor($this); // reference to self
}
/**
@@ -489,56 +621,270 @@ class e_user extends e_user_model
return true;
}
// TODO login by name/password, load, set cookie/session data
/**
* Get parent user ID - present if main admin is browsing
* front-end logged in as another user account
*
* @return integer or false if not present
*/
final public function getParentId()
{
return $this->_parent_id;
}
/**
* User login
* @param string $uname
* @param string $upass_plain
* @param boolean $uauto
* @param string $uchallange
* @return boolean success
*/
final public function login($uname, $upass_plain, $uauto = false, $uchallange = false)
{
// FIXME - rewrite userlogin - clean up redirects and
//$userlogin = new userlogin($uname, $upass_plain, $uauto, $uchallange);
// if($userlogin->getId()) $this->load() --> use the previously set user COOKIE/SESSION data
if($this->isUser()) return false;
$userlogin = new userlogin($uname, $upass_plain, $uauto, $uchallange, true);
$this->setSessionData(true)
->setData($userlogin->getUserData());
return $this->isUser();
}
final public function loginAs($user_id)
{
// TODO - set session data required for loadAs()
}
/**
*
* @return unknown_type
* @return e_user
*/
protected function initConstants()
protected function _initConstants()
{
//FIXME - BC - constants from init_session() should be defined here
//init_session(); // the old way
// [SecretR] Not sure we should do this here, it's too restricting - constants can be
// defined once, we need the freedom to do it multiple times - e.g. load() executed in constructor than login(), loginAs() etc.
// called by a controller
// We should switch to e.g. isAdmin() instead of ADMIN constant check
return $this;
}
/**
* TODO destroy cookie/session data, self destroy
* @return void
* Destroy cookie/session data, self destroy
* @return e_user
*/
final public function logout()
{
// FIXME - destoy cookie/session data first
$this->_data = array();
if (null !== $this->_extended_model)
$this->logoutAs()
->_destroySession();
parent::destroy();
if(session_id()) session_destroy();
e107::setRegistry('core/e107/current_user', null);
return $this;
}
/**
* Destroy cookie/session/model data for current user, resurrect parent user
* @return e_user
*/
final public function logoutAs()
{
if($this->getParentId())
{
$this->_extended_model->destroy();
// load parent user data
$this->_extended_model = $this->_parent_extmodel;
$this->_extended_structure = $this->_parent_extstruct;
$this->_user_config = $this->_parent_config;
$this->setData($this->_parent_model->getData());
// cleanup
$this->_destroyAsSession();
$this->_parent_id = false;
$this->_parent_model = $this->_parent_extstruct = $this->_parent_extmodel = $this->_parent_config = null;
}
e107::setRegistry('targets/core/current_user', null);
return $this;
}
/**
* TODO load user data by cookie/session data
* @return e_user
*/
final public function load($force = false)
final public function load($force = false, $denyAs = false)
{
// init_session() should come here
// $this->initConstants(); - called after data is loaded
// FIXME - temporary here, for testing only!!!
if(!$force && $this->getId()) return $this;
// always run cli as main admin
if(e107::isCli())
{
$this->_load(1, $force);
$this->_initConstants();
return $this;
}
// We have active session
if(null !== $this->_session_data)
{
list($uid, $upw) = explode('.', $this->_session_data);
// Bad cookie - destroy session
if(empty($uid) || !is_numeric($uid) || empty($upw))
{
$this->_destroyBadSession();
$this->_initConstants();
return $this;
}
$udata = $this->_load($uid, $force);
// Bad cookie - destroy session
if(empty($udata))
{
$this->_destroyBadSession();
$this->_initConstants();
return $this;
}
// we have a match
if(md5($udata['user_password']) == $upw)
{
// set current user data
$this->setData($udata);
// NEW - try 'logged in as' feature
if(!$denyAs) $this->loadAs();
$this->_initConstants();
return $this;
}
$this->_destroyBadSession();
$this->_initConstants();
return $this;
}
if (USER)
$this->setData(get_user_data(USERID));
return $this;
}
final public function loadAs()
{
// FIXME - option to avoid it when browsing Admin area
$loginAs = $this->_getSessionDataAs();
if(!$this->getParentId() && false !== $loginAs && $loginAs !== $this->getId() && $loginAs !== 1 && $this->isMainAdmin())
{
$uasdata = $this->_load($loginAs);
if(!empty($uasdata))
{
// backup parent user data to prevent further db queries
$this->_parent_id = $this->getId();
$this->_parent_model = new e_system_user($this->getData());
$this->setData($uasdata);
// not allowed - revert back
if($this->isMainAdmin())
{
$this->_parent_id = false;
$this->setData($this->_parent_model->getData());
$this->_parent_model = null;
$this->_destroyAsSession();
}
else
{
$this->_parent_extmodel = $this->_extended_model;
$this->_parent_extstruct = $this->_extended_structure;
$this->_user_config = $this->_parent_config;
$this->_extended_model = $this->_extended_structure = $this->_user_config = null;
}
}
}
else
{
$this->_parent_id = false;
$this->_parent_model = null;
$this->_parent_extstruct = $this->_parent_extmodel = null;
}
}
final protected function _destroySession()
{
cookie($this->_session_key, '', (time() - 2592000));
$_SESSION[$this->_session_key] = '';
return $this;
}
final protected function _destroyAsSession()
{
$key = $this->_session_key.'_as';
cookie($key, '', (time() - 2592000));
$_SESSION[$key] = '';
unset($_SESSION[$key]);
return $this;
}
final protected function _destroyBadSession()
{
$this->_session_error = true;
return $this->_destroySession();
}
final protected function _getSessionDataAs()
{
$id = false;
$key = $this->_session_key.'_as';
if('session' == $this->_session_type && isset($_SESSION[$key]) && !empty($_SESSION[$key]))
{
$id = $_SESSION[$key];
}
elseif('cookie' == $this->_session_type && isset($_COOKIE[$key]) && !empty($_COOKIE[$key]))
{
$id = $_COOKIE[$key];
}
if(!empty($id) && is_numeric($id)) return intval($id);
return false;
}
final public function setSessionData($force = false)
{
if($force || null === $this->_session_data)
{
$this->_session_key = e107::getPref('cookie_name', 'e107cookie');
$this->_session_type = e107::getPref('user_tracking', 'cookie');
if('session' == $this->_session_type && isset($_SESSION[$this->_session_key]) && !empty($_SESSION[$this->_session_key]))
{
$this->_session_data = &$_SESSION[$this->_session_key];
}
elseif('cookie' == $this->_session_type && isset($_COOKIE[$this->_session_key]) && !empty($_COOKIE[$this->_session_key]))
{
$this->_session_data = &$_COOKIE[$this->_session_key];
}
}
return $this;
}
public function hasSessionError()
{
return $this->_session_error;
}
final protected function _load($user_id)
{
if(e107::getDb()->db_Select('user', '*', 'user_id='.intval($user_id)))
{
return e107::getDb()->db_Fetch();
}
return array();
}
/**
* Not allowed
*
@@ -629,6 +975,14 @@ class e_user_extended_model extends e_front_model
->load();
}
/**
* Always return integer
*/
public function getId()
{
return (integer) parent::getId();
}
/**
* Get user model
* @return e_user_model
@@ -671,12 +1025,13 @@ class e_user_extended_model extends e_front_model
/**
* Get User extended field value
* Returns NULL when field/default value not found or not enough permissions
* @param string$field
* @param string $field
* @param boolean $short if true, 'user_' prefix will be added to field name
* @return mixed
*/
public function getValue($field)
public function getValue($field, $short = true)
{
$field = 'user_'.$field;
if($short) $field = 'user_'.$field;
if (!$this->checkRead($field))
return null;
return $this->get($field, $this->getDefault($field));
@@ -687,17 +1042,28 @@ class e_user_extended_model extends e_front_model
* Note: Data is not sanitized!
* @param string $field
* @param mixed $value
* @param boolean $short if true, 'user_' prefix will be added to field name
* @return e_user_extended_model
*/
public function setValue($field, $value)
public function setValue($field, $value, $short = true)
{
$field = 'user_'.$field;
if($short) $field = 'user_'.$field;
if (!$this->checkWrite($field))
return $this;
$this->set($field, $value, true);
return $this;
}
public function getReadData()
{
// TODO array allowed profile page data (read mode)
}
public function getWriteData()
{
// TODO array allowed settings page data (edit mode)
}
/**
* Get default field value, defined by extended field structure
* Returns NULL if field/default value not found
@@ -716,17 +1082,27 @@ class e_user_extended_model extends e_front_model
*/
public function checkRead($field)
{
return $this->getEditor()->checkClass(varset($this->_struct_index[$field]['read']));
$hidden = $this->get('user_hidden_fields');
if($this->getId() !== $this->getEditor()->getId() && !empty($hidden) && strpos($hidden, $field) !== false) return false;
return ($this->checkApplicable($field) && $this->getEditor()->checkClass(varset($this->_struct_index[$field]['read'])));
}
/**
* Check field write permissions
* Check field write permissions against current editor
* @param string $field
* @return boolean
*/
public function checkWrite($field)
{
return $this->getEditor()->checkClass(varset($this->_struct_index[$field]['write']));
if(!$this->checkApplicable($field)) return false;
$editor = $this->getEditor();
// Main admin checked later in checkClass() method
if($editor->checkAdminPerms('4') && varset($this->_struct_index[$field]['write']) != e_UC_NOBODY)
return true;
return $editor->checkClass(varset($this->_struct_index[$field]['write']));
}
/**
@@ -736,17 +1112,17 @@ class e_user_extended_model extends e_front_model
*/
public function checkSignup($field)
{
return $this->getEditor()->checkClass(varset($this->_struct_index[$field]['signup']));
return $this->getUser()->checkClass(varset($this->_struct_index[$field]['signup']));
}
/**
* Check field applicable permissions
* Check field applicable permissions against current user
* @param string $field
* @return boolean
*/
public function checkApplicable($field)
{
return $this->getEditor()->checkClass(varset($this->_struct_index[$field]['applicable']));
return $this->getUser()->checkClass(varset($this->_struct_index[$field]['applicable']));
}
/**
@@ -774,6 +1150,7 @@ class e_user_extended_model extends e_front_model
{
// load structure dependencies
$ignore = array($this->getFieldIdName(), 'user_hidden_fields'); // TODO - user_hidden_fields? Old?
$fields = $struct_tree->getTree();
foreach ($fields as $id => $field)
{
@@ -849,9 +1226,28 @@ class e_user_extended_model extends e_front_model
return $this->_structure;
}
/**
* Additional security while applying posted
* data to user extended model
* @return e_user_extended_model
*/
public function mergePostedData()
{
$posted = $this->getPostedData();
foreach ($posted as $key => $value)
{
if(!$this->checkWrite($key))
{
$this->removePosted($key);
}
}
parent::mergePostedData(true, true, true);
return $this;
}
/**
* Build data types and rules on the fly and save
* @see e107_handlers/e_front_model#save($from_post, $force, $session_messages)
* @see e_front_model::save()
*/
public function save($force = false, $session = false)
{
@@ -859,10 +1255,13 @@ class e_user_extended_model extends e_front_model
return parent::save(true, $force, $session);
}
/**
* Doesn't save anything actually...
*/
public function saveDebug($retrun = false, $undo = true)
{
$this->_buildManageRules();
parent::saveDebug($return, $undo);
return parent::saveDebug($return, $undo);
}
}
@@ -1011,3 +1410,80 @@ class e_user_extended_structure_tree extends e_tree_model
return $this;
}
}
class e_user_pref extends e_model
{
/**
* @var e_user_model
*/
protected $_user;
/**
* Constructor
* @param e_user_model $user_model
* @return void
*/
public function __construct(e_user_model $user_model)
{
$this->_user = $user_model;
$this->load();
}
/**
* Load data from user preferences string
* @param boolean $force
* @return e_user_pref
*/
public function load($force = false)
{
if($force || !$this->hasData())
{
$data = $this->_user->get('user_prefs', '');
if(!empty($data))
{
$data = e107::getArrayStorage()->ReadArray($data);
if(!$data) $data = array();
}
else $data = array();
$this->setData($data);
}
return $this;
}
/**
* Apply current data to user data
* @return e_user_pref
*/
public function apply()
{
$this->_user->set('user_prefs', $this->toString(true));
return $this;
}
/**
* Save and apply user preferences
* @return boolean success
*/
public function save()
{
if($this->_user->getId())
{
$data = $this->toString(true);
$this->apply();
return (e107::getDb('user_prefs')->db_Update('user', "user_prefs='{$data}' WHERE user_id=".$this->_user->getId()) ? true : false);
}
return false;
}
/**
* Remove & apply user prefeferences, optionally - save to DB
* @return boolean success
*/
public function delete($save = false)
{
$this->removeData()->apply();
if($save) return $this->save();
return true;
}
}