PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/backend/core/engine/authentication.php

http://github.com/forkcms/forkcms
PHP | 354 lines | 161 code | 56 blank | 137 comment | 23 complexity | cd4d55aa24390ebe489256e6b97f6f0e MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, MIT, AGPL-3.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /*
  3. * This file is part of Fork CMS.
  4. *
  5. * For the full copyright and license information, please view the license
  6. * file that was distributed with this source code.
  7. */
  8. /**
  9. * The class below will handle all authentication stuff. It will handle module-access, action-acces, ...
  10. *
  11. * @author Tijs Verkoyen <tijs@sumocoders.be>
  12. * @author Davy Hellemans <davy.hellemans@netlash.com>
  13. */
  14. class BackendAuthentication
  15. {
  16. /**
  17. * All allowed modules
  18. *
  19. * @var array
  20. */
  21. private static $allowedActions = array();
  22. /**
  23. * All allowed modules
  24. *
  25. * @var array
  26. */
  27. private static $allowedModules = array();
  28. /**
  29. * A userobject for the current authenticated user
  30. *
  31. * @var BackendUser
  32. */
  33. private static $user;
  34. /**
  35. * Cleanup sessions for the current user and sessions that are invalid
  36. */
  37. public static function cleanupOldSessions()
  38. {
  39. // remove all sessions that are invalid (older then 30 min)
  40. BackendModel::getDB(true)->delete('users_sessions', 'date <= DATE_SUB(NOW(), INTERVAL 30 MINUTE)');
  41. }
  42. /**
  43. * Returns the encrypted password for a user by giving a email/password
  44. * Returns false if no user was found for this user/pass combination
  45. *
  46. * @param string $email The email.
  47. * @param string $password The password.
  48. * @return string
  49. */
  50. public static function getEncryptedPassword($email, $password)
  51. {
  52. $email = (string) $email;
  53. $password = (string) $password;
  54. // fetch user ID by email
  55. $userId = BackendUsersModel::getIdByEmail($email);
  56. // check if a user ID was found, return false if no user exists
  57. if($userId === false) return false;
  58. // fetch user record
  59. $user = new BackendUser($userId);
  60. $key = $user->getSetting('password_key');
  61. // return the encrypted string
  62. return (string) self::getEncryptedString($password, $key);
  63. }
  64. /**
  65. * Returns a string encrypted like sha1(md5($salt) . md5($string))
  66. * The salt is an optional extra string you can strenghten your encryption with
  67. *
  68. * @param string $string The string to encrypt.
  69. * @param string[optional] $salt The salt to use.
  70. * @return string
  71. */
  72. public static function getEncryptedString($string, $salt = null)
  73. {
  74. $string = (string) $string;
  75. $salt = (string) $salt;
  76. // return the encrypted string
  77. return (string) sha1(md5($salt) . md5($string));
  78. }
  79. /**
  80. * Returns the current authenticated user
  81. *
  82. * @return BackendUser
  83. */
  84. public static function getUser()
  85. {
  86. // if the user-object doesn't exist create a new one
  87. if(self::$user === null) self::$user = new BackendUser();
  88. return self::$user;
  89. }
  90. /**
  91. * Is the given action allowed for the current user
  92. *
  93. * @param string $action The action to check for.
  94. * @param string $module The module wherin the action is located.
  95. * @return bool
  96. */
  97. public static function isAllowedAction($action = null, $module = null)
  98. {
  99. // GOD's rule them all!
  100. if(self::getUser()->isGod()) return true;
  101. // always allowed actions (yep, hardcoded, because we don't want other people to fuck up)
  102. $alwaysAllowed = array(
  103. 'dashboard' => array('index' => 7),
  104. 'core' => array('generate_url' => 7, 'content_css' => 7),
  105. 'error' => array('index' => 7),
  106. 'authentication' => array('index' => 7, 'reset_password' => 7, 'logout' => 7)
  107. );
  108. // grab the URL from the reference
  109. $URL = Spoon::get('url');
  110. $action = ($action !== null) ? (string) $action : $URL->getAction();
  111. $module = ($module !== null) ? (string) $module : $URL->getModule();
  112. // is this action an action that doesn't require authentication?
  113. if(isset($alwaysAllowed[$module][$action])) return true;
  114. // we will cache everything
  115. if(empty(self::$allowedActions))
  116. {
  117. // init var
  118. $db = BackendModel::getDB();
  119. // get modules
  120. $modules = BackendModel::getModules();
  121. // add always allowed
  122. foreach($alwaysAllowed as $allowedModule => $actions) $modules[] = $allowedModule;
  123. // get allowed actions
  124. $allowedActionsRows = (array) $db->getRecords(
  125. 'SELECT gra.module, gra.action, MAX(gra.level) AS level
  126. FROM users_sessions AS us
  127. INNER JOIN users AS u ON us.user_id = u.id
  128. INNER JOIN users_groups AS ug ON u.id = ug.user_id
  129. INNER JOIN groups_rights_actions AS gra ON ug.group_id = gra.group_id
  130. WHERE us.session_id = ? AND us.secret_key = ?
  131. GROUP BY gra.module, gra.action',
  132. array(SpoonSession::getSessionId(), SpoonSession::get('backend_secret_key'))
  133. );
  134. // add all actions and there level
  135. foreach($allowedActionsRows as $row)
  136. {
  137. // add if the module is installed
  138. if(in_array($row['module'], $modules)) self::$allowedActions[$row['module']][$row['action']] = (int) $row['level'];
  139. }
  140. }
  141. // do we know a level for this action
  142. if(isset(self::$allowedActions[$module][$action]))
  143. {
  144. // is the level greater than zero? aka: do we have access?
  145. if((int) self::$allowedActions[$module][$action] > 0) return true;
  146. }
  147. // fallback
  148. return false;
  149. }
  150. /**
  151. * Is the given module allowed for the current user
  152. *
  153. * @param string $module The module to check for.
  154. * @return bool
  155. */
  156. public static function isAllowedModule($module)
  157. {
  158. // GOD's rule them all!
  159. if(self::isLoggedIn() && self::getUser()->isGod()) return true;
  160. // always allowed modules (yep, hardcoded, because, we don't want other people to fuck up)
  161. $alwaysAllowed = array('core', 'error', 'authentication');
  162. // redefine
  163. $module = (string) $module;
  164. // is this module a module that doesn't require authentication?
  165. if(in_array($module, $alwaysAllowed)) return true;
  166. // do we already know something?
  167. if(empty(self::$allowedModules))
  168. {
  169. // init var
  170. $db = BackendModel::getDB();
  171. // get allowed modules
  172. $allowedModules = $db->getColumn(
  173. 'SELECT DISTINCT grm.module
  174. FROM users_sessions AS us
  175. INNER JOIN users AS u ON us.user_id = u.id
  176. INNER JOIN users_groups AS ug ON u.id = ug.user_id
  177. INNER JOIN groups_rights_modules AS grm ON ug.group_id = grm.group_id
  178. WHERE us.session_id = ? AND us.secret_key = ?',
  179. array(SpoonSession::getSessionId(), SpoonSession::get('backend_secret_key'))
  180. );
  181. // add all modules
  182. foreach($allowedModules as $row) self::$allowedModules[$row] = true;
  183. }
  184. // not available in our cache
  185. if(!isset(self::$allowedModules[$module])) return false;
  186. // return value that was stored in cache
  187. else return self::$allowedModules[$module];
  188. }
  189. /**
  190. * Is the current user logged in?
  191. *
  192. * @return bool
  193. */
  194. public static function isLoggedIn()
  195. {
  196. // check if all needed values are set in the session
  197. // @todo could be written by SpoonSession::get (since that no longer throws exceptions)
  198. if(SpoonSession::exists('backend_logged_in', 'backend_secret_key') && (bool) SpoonSession::get('backend_logged_in') && (string) SpoonSession::get('backend_secret_key') != '')
  199. {
  200. // get database instance
  201. $db = BackendModel::getDB(true);
  202. // get the row from the tables
  203. $sessionData = $db->getRecord(
  204. 'SELECT us.id, us.user_id
  205. FROM users_sessions AS us
  206. WHERE us.session_id = ? AND us.secret_key = ?
  207. LIMIT 1',
  208. array(SpoonSession::getSessionId(), SpoonSession::get('backend_secret_key'))
  209. );
  210. // if we found a matching row, we know the user is logged in, so we update his session
  211. if($sessionData !== null)
  212. {
  213. // update the session in the table
  214. $db->update('users_sessions', array('date' => BackendModel::getUTCDate()), 'id = ?', (int) $sessionData['id']);
  215. // create a user object, it will handle stuff related to the current authenticated user
  216. self::$user = new BackendUser($sessionData['user_id']);
  217. // the user is logged on
  218. return true;
  219. }
  220. // no data found, so fuck up the session, will be handled later on in the code
  221. else SpoonSession::set('backend_logged_in', false);
  222. }
  223. // no data found, so fuck up the session, will be handled later on in the code
  224. else SpoonSession::set('backend_logged_in', false);
  225. // reset values for invalid users. We can't destroy the session because session-data can be used on the site.
  226. if((bool) SpoonSession::get('backend_logged_in') === false)
  227. {
  228. // reset some values
  229. SpoonSession::set('backend_logged_in', false);
  230. SpoonSession::set('backend_secret_key', '');
  231. // return result
  232. return false;
  233. }
  234. }
  235. /**
  236. * Login the user with the given credentials.
  237. * Will return a boolean that indicates if the user is logged in.
  238. *
  239. * @param string $login The users login.
  240. * @param string $password The password provided by the user.
  241. * @return bool
  242. */
  243. public static function loginUser($login, $password)
  244. {
  245. $login = (string) $login;
  246. $password = (string) $password;
  247. $db = BackendModel::getDB(true);
  248. // fetch the encrypted password
  249. $passwordEncrypted = BackendAuthentication::getEncryptedPassword($login, $password);
  250. // check in database (is the user active and not deleted, are the email and password correct?)
  251. $userId = (int) $db->getVar(
  252. 'SELECT u.id
  253. FROM users AS u
  254. WHERE u.email = ? AND u.password = ? AND u.active = ? AND u.deleted = ?
  255. LIMIT 1',
  256. array($login, $passwordEncrypted, 'Y', 'N')
  257. );
  258. // not 0 = valid user!
  259. if($userId !== 0)
  260. {
  261. // cleanup old sessions
  262. self::cleanupOldSessions();
  263. // build the session array (will be stored in the database)
  264. $session = array();
  265. $session['user_id'] = $userId;
  266. $session['secret_key'] = BackendAuthentication::getEncryptedString(SpoonSession::getSessionId(), $userId);
  267. $session['session_id'] = SpoonSession::getSessionId();
  268. $session['date'] = BackendModel::getUTCDate();
  269. // insert a new row in the session-table
  270. $db->insert('users_sessions', $session);
  271. // store some values in the session
  272. SpoonSession::set('backend_logged_in', true);
  273. SpoonSession::set('backend_secret_key', $session['secret_key']);
  274. // return result
  275. return true;
  276. }
  277. // userId 0 will not exist, so it means that this isn't a valid combination
  278. else
  279. {
  280. // reset values for invalid users. We can't destroy the session because session-data can be used on the site.
  281. SpoonSession::set('backend_logged_in', false);
  282. SpoonSession::set('backend_secret_key', '');
  283. // return result
  284. return false;
  285. }
  286. }
  287. /**
  288. * Logsout the current user
  289. */
  290. public static function logout()
  291. {
  292. // remove all rows owned by the current user
  293. BackendModel::getDB(true)->delete('users_sessions', 'session_id = ?', SpoonSession::getSessionId());
  294. // reset values. We can't destroy the session because session-data can be used on the site.
  295. SpoonSession::set('backend_logged_in', false);
  296. SpoonSession::set('backend_secret_key', '');
  297. }
  298. }