AutoLoginComponent.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. App::uses('Component', 'Controller');
  3. /**
  4. * AutoLoginComponent
  5. *
  6. * A CakePHP Component that will automatically login the Auth session for a duration if the user requested to (saves data to cookies).
  7. *
  8. * @author Miles Johnson - http://milesj.me
  9. * @copyright Copyright 2006-2011, Miles Johnson, Inc.
  10. * @license http://opensource.org/licenses/mit-license.php - Licensed under The MIT License
  11. * @link http://milesj.me/code/cakephp/auto-login
  12. *
  13. * @modified Mark Scherer - 2012-01-08 ms
  14. * - now works with Controller::beforeFilter() modifications to allow username/email login switch
  15. * - can be disabled dynamically and will skip on CakeError view
  16. */
  17. class AutoLoginComponent extends Component {
  18. /**
  19. * Current version.
  20. *
  21. * @access public
  22. * @var string
  23. */
  24. public $version = '3.5';
  25. /**
  26. * Components.
  27. *
  28. * @access public
  29. * @var array
  30. */
  31. public $components = array('Auth', 'Cookie');
  32. /**
  33. * Settings.
  34. *
  35. * @access public
  36. * @var array
  37. */
  38. public $settings = array();
  39. /**
  40. * Default settings.
  41. *
  42. * @access protected
  43. * @var array
  44. */
  45. protected $_defaults = array(
  46. 'active' => true,
  47. 'model' => 'User',
  48. 'username' => 'username',
  49. 'password' => 'password',
  50. 'plugin' => '',
  51. 'controller' => 'users',
  52. 'loginAction' => 'login',
  53. 'logoutAction' => 'logout',
  54. 'cookieName' => 'autoLogin',
  55. 'expires' => '+2 weeks', # Cookie length (strtotime() format)
  56. 'redirect' => true,
  57. 'requirePrompt' => true, # Displayed checkbox determines if cookie is created
  58. 'debug' => null # Auto-Select based on debug mode or ip range
  59. );
  60. /**
  61. * Determines whether to trigger startup() logic.
  62. *
  63. * @access protected
  64. * @var boolean
  65. */
  66. protected $_isValidRequest = false;
  67. /**
  68. * Initialize settings and debug.
  69. *
  70. * @param ComponentCollection $collection
  71. * @param array $settings
  72. */
  73. public function __construct(ComponentCollection $collection, $settings = array()) {
  74. $defaultSettings = array_merge($this->_defaults, (array)Configure::read('AutoLogin'));
  75. $settings = array_merge($defaultSettings, $settings);
  76. # make sure an upgrade does reset all cookies stored to avoid conflicts
  77. $settings['cookieName'] = $settings['cookieName'].str_replace('.', '', $this->version);
  78. $this->settings = $settings;
  79. parent::__construct($collection, $settings);
  80. }
  81. /**
  82. * Detect debug info.
  83. *
  84. * @access public
  85. * @param Controller $controller
  86. * @return void
  87. */
  88. public function initialize(Controller $controller) {
  89. if ($controller->name == 'CakeError' || !$this->settings['active']) {
  90. return;
  91. }
  92. // Validate the cookie
  93. $cookie = $this->_readCookie();
  94. $user = $this->Auth->user();
  95. if (!empty($user) || !$cookie || !$controller->request->is('get')) {
  96. return;
  97. }
  98. // Is debug enabled
  99. if ($this->settings['debug'] === null) {
  100. $this->settings['debug'] = Configure::read('debug') > 0 || !empty($this->settings['ips']) && in_array(env('REMOTE_ADDR'), (array)$this->settings['ips']);
  101. }
  102. if (empty($cookie['hash']) || $cookie['hash'] != $this->Auth->password($cookie['username'] . $cookie['time'])) {
  103. $this->debug('hashFail', $cookie, $user);
  104. $this->delete();
  105. return;
  106. }
  107. // Set the data to identify with
  108. $controller->request->data[$this->settings['model']][$this->settings['username']] = $cookie['username'];
  109. $controller->request->data[$this->settings['model']][$this->settings['password']] = $cookie['password'];
  110. // Request is valid, stop startup()
  111. $this->_isValidRequest = true;
  112. }
  113. /**
  114. * Automatically login existent Auth session; called after controllers beforeFilter() so that Auth is initialized.
  115. *
  116. * @access public
  117. * @param Controller $controller
  118. * @return void
  119. */
  120. public function startup(Controller $controller) {
  121. if (!$this->_isValidRequest) {
  122. return;
  123. }
  124. if ($this->Auth->login()) {
  125. $this->debug('login', $this->Cookie, $this->Auth->user());
  126. if (in_array('_autoLogin', get_class_methods($controller))) {
  127. call_user_func_array(array($controller, '_autoLogin'), array(
  128. $this->Auth->user()
  129. ));
  130. }
  131. if ($this->settings['redirect']) {
  132. $controller->redirect(array(), 301);
  133. }
  134. } else {
  135. $this->debug('loginFail', $this->Cookie, $this->Auth->user());
  136. if (in_array('_autoLoginError', get_class_methods($controller))) {
  137. call_user_func_array(array($controller, '_autoLoginError'), array(
  138. $this->_readCookie()
  139. ));
  140. }
  141. }
  142. }
  143. /**
  144. * Automatically process logic when hitting login/logout actions.
  145. *
  146. * @access public
  147. * @uses Inflector
  148. * @param Controller $controller
  149. * @return void
  150. */
  151. public function beforeRedirect(Controller $controller, $url, $status = null, $exit = true) {
  152. if (!$this->settings['active']) {
  153. return;
  154. }
  155. $model = $this->settings['model'];
  156. if (is_array($this->Auth->loginAction)) {
  157. if (!empty($this->Auth->loginAction['controller'])) {
  158. $this->settings['controller'] = $this->Auth->loginAction['controller'];
  159. }
  160. if (!empty($this->Auth->loginAction['action'])) {
  161. $this->settings['loginAction'] = $this->Auth->loginAction['action'];
  162. }
  163. if (!empty($this->Auth->loginAction['plugin'])) {
  164. $this->settings['plugin'] = $this->Auth->loginAction['plugin'];
  165. }
  166. }
  167. if (empty($this->settings['controller'])) {
  168. $this->settings['controller'] = Inflector::pluralize($model);
  169. }
  170. // Is called after user login/logout validates, but before auth redirects
  171. if ($controller->plugin == Inflector::camelize($this->settings['plugin']) && $controller->name == Inflector::camelize($this->settings['controller'])) {
  172. $data = $controller->request->data;
  173. $action = isset($controller->request->params['action']) ? $controller->request->params['action'] : 'login';
  174. switch ($action) {
  175. case $this->settings['loginAction']:
  176. if (isset($data[$model])) {
  177. $username = $data[$model][$this->settings['username']];
  178. $password = $data[$model][$this->settings['password']];
  179. $autoLogin = isset($data[$model]['auto_login']) ? $data[$model]['auto_login'] : !$this->settings['requirePrompt'];
  180. if (!empty($username) && !empty($password) && $autoLogin) {
  181. $this->_writeCookie($username, $password);
  182. } elseif (!$autoLogin) {
  183. $this->delete();
  184. }
  185. }
  186. break;
  187. case $this->settings['logoutAction']:
  188. $this->debug('logout', $this->Cookie, $this->Auth->user());
  189. $this->delete();
  190. break;
  191. }
  192. }
  193. }
  194. /**
  195. * Delete the cookie.
  196. *
  197. * @access public
  198. * @return void
  199. */
  200. public function delete() {
  201. $this->Cookie->delete($this->settings['cookieName']);
  202. }
  203. /**
  204. * Debug the current auth and cookies.
  205. *
  206. * @access public
  207. * @param string $key
  208. * @param array $cookie
  209. * @param array $user
  210. * @return void
  211. */
  212. public function debug($key, $cookie = array(), $user = array()) {
  213. $scopes = array(
  214. 'login' => 'Login Successful',
  215. 'loginFail' => 'Login Failure',
  216. 'loginCallback' => 'Login Callback',
  217. 'logout' => 'Logout',
  218. 'logoutCallback' => 'Logout Callback',
  219. 'cookieSet' => 'Cookie Set',
  220. 'cookieFail' => 'Cookie Mismatch',
  221. 'hashFail' => 'Hash Mismatch',
  222. 'custom' => 'Custom Callback'
  223. );
  224. if ($this->settings['debug'] && isset($scopes[$key])) {
  225. $debug = (array)Configure::read('AutoLogin');
  226. $content = "";
  227. if (!empty($cookie) || !empty($user)) {
  228. if (!empty($cookie)) {
  229. $content .= "Cookie information: \n\n" . print_r($cookie, true) . "\n\n\n";
  230. }
  231. if (!empty($user)) {
  232. $content .= "User information: \n\n" . print_r($user, true);
  233. }
  234. } else {
  235. $content = 'No debug information.';
  236. }
  237. if (empty($debug['scope']) || in_array($key, (array)$debug['scope'])) {
  238. if (!empty($debug['email'])) {
  239. mail($debug['email'], '[AutoLogin] ' . $scopes[$key], $content, 'From: ' . $debug['email']);
  240. } else {
  241. $this->log($scopes[$key] . ': ' . $content, 'autologin');
  242. }
  243. }
  244. }
  245. }
  246. /**
  247. * Remember the user information and store it in a cookie (encrypted).
  248. *
  249. * @param string $username
  250. * @param string $password
  251. * @return void
  252. */
  253. protected function _writeCookie($username, $password) {
  254. $time = time();
  255. $cookie = array();
  256. $cookie['username'] = base64_encode($username);
  257. $cookie['password'] = base64_encode($password);
  258. $cookie['hash'] = $this->Auth->password($username . $time);
  259. $cookie['time'] = $time;
  260. if (env('REMOTE_ADDR') === '127.0.0.1' || env('HTTP_HOST') === 'localhost') {
  261. $this->Cookie->domain = false;
  262. }
  263. $this->Cookie->write($this->settings['cookieName'], $cookie, true, $this->settings['expires']);
  264. $this->debug('cookieSet', $cookie, $this->Auth->user());
  265. }
  266. /**
  267. * Read cookie and decode it
  268. *
  269. * @return mixed array $cookieData or false on failure
  270. */
  271. protected function _readCookie() {
  272. $cookie = $this->Cookie->read($this->settings['cookieName']);
  273. if (empty($cookie) || !is_array($cookie)) {
  274. return false;
  275. }
  276. if (isset($cookie['username'])) {
  277. $cookie['username'] = base64_decode($cookie['username']);
  278. }
  279. if (isset($cookie['password'])) {
  280. $cookie['password'] = base64_decode($cookie['password']);
  281. }
  282. return $cookie;
  283. }
  284. }