AutoLoginComponent.php 8.6 KB

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