* @copyright Copyright 2007-2014, Joshua Sherman * @license http://www.opensource.org/licenses/mit-license.html * @package PICKLES * @link https://github.com/joshtronic/pickles */ /** * Security Class * * Collection of static methods for handling security within a website running * on PICKLES. Requires sessions to be enabled. * * @usage Security::login(10); * @usage Security::isLevel(SECURITY_LEVEL_ADMIN); */ class Security { /** * Lookup Cache * * Used to minimize database lookups * * @static * @access private * @var array */ private static $cache = array(); /** * Generate Hash * * Generates an SHA1 hash from the provided string. Salt optional. * * @param string $source value to hash * @param mixed $salts optional salt or salts * @return string SHA1 hash */ public static function generateHash($source, $salts = null) { // Determines which salt(s) to use if ($salts == null) { $config = Config::getInstance(); if (isset($config->security['salt']) && $config->security['salt'] != null) { $salts = $config->security['salt']; } else { $salts = array('P1ck73', 'Ju1C3'); } } // Forces the variable to be an array if (!is_array($salts)) { $salts = array($salts); } // Loops through the salts, applies them and calculates the hash $hash = $source; foreach ($salts as $salt) { $hash = sha1($salt . $hash); } return $hash; } /** * SHA-256 * * Generates an SHA-256 hash from the provided string. * * @param string $source value to hash * @return string SHA1 hash */ public static function sha256($source) { return hash('sha256', $source); } /** * Generate SHA-256 Hash * * Generates an SHA-256 hash from the provided string and salt. Borrowed the * large iteration logic from fCryptography::hashWithSalt() as, and I quote, * "makes rainbow table attacks infesible". * * @param string $source value to hash * @param mixed $salt value to use as salt * @return string SHA-256 hash * @link https://github.com/flourishlib/flourish-classes/blob/master/fCryptography.php */ public static function generateSHA256Hash($source, $salt) { $sha256 = sha1($salt . $source); for ($i = 0; $i < 1000; $i++) { $sha256 = Security::sha256($sha256 . (($i % 2 == 0) ? $source : $salt)); } return $sha256; } /** * Check Session * * Checks if sessions are enabled. * * @static * @access private * @return boolean whether or not sessions are enabled */ private static function checkSession() { if (session_id() == '') { return false; } else { return true; } } /** * Check Level * * Checks if a passed level is an integer and/or properly defined in the * site's configuration file. * * @static * @access private * @param mixed access level to validate * @return whether ot not the access level is valid */ private static function checkLevel(&$access_level) { if (is_int($access_level)) { return true; } else { $config = Config::getInstance(); // Attempts to validate the string passed if (isset($config->security[$access_level])) { if (is_numeric($config->security[$access_level])) { $access_level = (int)$config->security[$access_level]; return true; } else { throw new Exception('Level "' . $access_level . '" is not numeric in config.ini'); } } else { throw new Exception('Level "' . $access_level . '" is not defined in config.ini'); } } return false; } /** * Login * * Creates a session variable containing the user ID and generated token. * The token is also assigned to a cookie to be used when validating the * security level. When the level value is present, the class will by pass * the database look up and simply use that value when validating (the less * paranoid scenario). * * @static * @param integer $user_id ID of the user that's been logged in * @param integer $level optional level for the user being logged in * @param string $role textual representation of the user's level * @return boolean whether or not the login could be completed */ public static function login($user_id, $level = null, $role = null) { if (self::checkSession()) { $token = sha1(microtime()); $_SESSION['__pickles']['security'] = array( 'token' => $token, 'user_id' => (int)$user_id, 'level' => $level, 'role' => $role, ); setcookie('pickles_security_token', $token); return true; } else { return false; } } /** * Logout * * Clears out the security information in the session and the cookie. * * @static * @return boolean true */ public static function logout() { if (isset($_SESSION['__pickles']['security'])) { $_SESSION['__pickles']['security'] = null; unset($_SESSION['__pickles']['security']); setcookie('pickles_security_token', '', time() - 3600); } return true; } /** * Get User Level * * Looks up the user level in the database and caches it. Cache is used * for any subsequent look ups for the user. Also validates the session * variable against the cookie to ensure everything is legit. If the user * level is set in the session, that value will take precedence. * * return integer user level or false */ private static function getUserLevel() { if (self::checkSession() == true && isset($_SESSION['__pickles']['security']['user_id'])) { // Checks the session against the cookie if (isset($_SESSION['__pickles']['security']['token'], $_COOKIE['pickles_security_token']) && $_SESSION['__pickles']['security']['token'] != $_COOKIE['pickles_security_token']) { Security::logout(); } elseif (isset($_SESSION['__pickles']['security']['level']) && $_SESSION['__pickles']['security']['level'] != null) { return $_SESSION['__pickles']['security']['level']; } // Hits the database to determine the user's level else { // Checks the session cache instead of hitting the database if (isset($_SESSION['__pickles']['security']['user_id'], self::$cache[(int)$_SESSION['__pickles']['security']['user_id']])) { return self::$cache[(int)$_SESSION['__pickles']['security']['user_id']]; } else { // Pulls the config and defaults where necessary $config = Config::getInstance(); if ($config->security === false) { $config = array(); } else { $config = $config->security; } $defaults = array('login' => 'login', 'model' => 'User', 'column' => 'level'); foreach ($defaults as $variable => $value) { if (!isset($config[$variable])) { $config[$variable] = $value; } } // Uses the model to pull the user's access level $class = $config['model']; $model = new $class(array('fields' => $config['column'], 'conditions' => array('id' => (int)$_SESSION['__pickles']['security']['user_id']))); if ($model->count() == 0) { Security::logout(); } else { $constant = 'SECURITY_LEVEL_' . $model->record[$config['column']]; if (defined($constant)) { $constant = constant($constant); self::$cache[(int)$_SESSION['__pickles']['security']['user_id']] = $constant; return $constant; } else { throw new Exception('Security level constant is not defined'); } } } } } return false; } /** * Is Level * * Checks the user's access level is exactly the passed level * * @static * @param integer $access_level access level to be checked against * @return boolean whether or not the user is that level */ public static function isLevel() { $is_level = false; if (self::checkSession()) { $arguments = func_get_args(); if (is_array($arguments[0])) { $arguments = $arguments[0]; } foreach ($arguments as $access_level) { if (self::checkLevel($access_level)) { if (self::getUserLevel() == $access_level) { $is_level = true; break; } } } } return $is_level; } /** * Has Level * * Checks the user's access level against the passed level. * * @static * @param integer $access_level access level to be checked against * @return boolean whether or not the user has access */ public static function hasLevel() { $has_level = false; if (self::checkSession()) { $arguments = func_get_args(); if (is_array($arguments[0])) { $arguments = $arguments[0]; } foreach ($arguments as $access_level) { if (self::checkLevel($access_level)) { if (self::getUserLevel() >= $access_level) { $has_level = true; break; } } } } return $has_level; } /** * Between Level * * Checks the user's access level against the passed range. * * @static * @param integer $low access level to be checked against * @param integer $high access level to be checked against * @return boolean whether or not the user has access */ public static function betweenLevel($low, $high) { $between_level = false; if (self::checkSession()) { if (self::checkLevel($low) && self::checkLevel($high)) { $user_level = self::getUserLevel(); if ($user_level >= $low && $user_level <= $high) { $between_level = true; break; } } } return $between_level; } } ?>