* @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 = []; /** * 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 * @todo Transition away from this */ 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 = ['P1ck73', 'Ju1C3']; } } // Forces the variable to be an array if (!is_array($salts)) { $salts = [$salts]; } // Loops through the salts, applies them and calculates the hash $hash = $source; foreach ($salts as $salt) { $hash = sha1($salt . $hash); } return $hash; } /** * 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 = hash('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) { return is_int($access_level); } /** * 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'] = [ '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']; } // Used to hit the database to determine the user's level, found it // to be overkill and just opted for a simple logout. else { Security::logout(); } } 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; } } } } 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; } } } } 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; } } } return $between_level; } } ?>