AutoLoginComponent.php 8.6 KB

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