AuthExtComponent.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <?php
  2. if (!defined('USER_ROLE_KEY')) {
  3. define('USER_ROLE_KEY', 'Role');
  4. }
  5. if (!defined('USER_INFO_KEY')) {
  6. define('USER_INFO_KEY', 'Info');
  7. }
  8. if (!defined('USER_RIGHT_KEY')) {
  9. define('USER_RIGHT_KEY', 'Right');
  10. }
  11. if (!defined('CLASS_USER')) {
  12. define('CLASS_USER', 'User');
  13. }
  14. App::uses('AuthComponent', 'Controller/Component');
  15. /**
  16. * Important:
  17. * index the ACO on alias, index the Aro on model+id
  18. *
  19. * Extends AuthComponent with the following addons:
  20. * - allows multiple roles per user
  21. * - auto-raises login counter and sets last_login date
  22. * - preps the session data according to completeAuth() method (adds parent data etc)
  23. * - dynamic login scope validation
  24. *
  25. * @author Mark Scherer
  26. * @cakephp 2.0
  27. * @license MIT
  28. * 2011-12-18 ms
  29. */
  30. class AuthExtComponent extends AuthComponent {
  31. public $intermediateModel = 'RoleUser';
  32. public $roleModel = 'Role';
  33. public $fieldKey = 'role_id';
  34. public $loginAction = array('controller' => 'account', 'action' => 'login', 'admin' => false, 'plugin' => false);
  35. public $loginRedirect = array('controller' => 'overview', 'action' => 'home', 'admin' => false, 'plugin' => false);
  36. public $autoRedirect = false;
  37. public $loginError = null;
  38. public $settings = array(
  39. 'multi' => null, # null=auto - yes/no multiple roles (HABTM table between users and roles)
  40. 'parentModelAlias' => USER_ROLE_KEY,
  41. 'userModel' => CLASS_USER
  42. );
  43. # field name in DB , if none is specified there will be no floodProtection
  44. public $floodProtection = null;
  45. public function __construct(ComponentCollection $Collection, $settings = array()) {
  46. $settings = array_merge($this->settings, (array) $settings, (array) Configure::read('Auth'));
  47. $this->Controller = $Collection->getController();
  48. parent::__construct($Collection, $settings);
  49. # auto-select multi if necessary
  50. if ($this->settings['multi'] === null) {
  51. $Model = $this->getModel();
  52. if (!empty($Model->hasMany)) {
  53. foreach ($Model->hasMany as $name => $relation) {
  54. if ($name != $this->roleModel) {
  55. continue;
  56. }
  57. $this->settings['multi'] = false;
  58. return;
  59. }
  60. }
  61. $Model = $this->getModel();
  62. if (!empty($Model->hasAndBelongsToMany)) {
  63. foreach ($Model->hasAndBelongsToMany as $name => $relation) {
  64. if ($name != $this->roleModel) {
  65. continue;
  66. }
  67. $this->settings['multi'] = true;
  68. return;
  69. }
  70. }
  71. //$this->log('AuthExt component not configured properly (auto select multi failed)');
  72. }
  73. }
  74. public function initialize(Controller $Controller) {
  75. $this->Controller = $Controller;
  76. parent::initialize($Controller);
  77. }
  78. /**
  79. * 2.1 fix for allowing * as wildcard
  80. * 2012-01-10 ms
  81. */
  82. public function allow($action = null) {
  83. if (((array) $action) === array('*')) {
  84. parent::allow();
  85. return;
  86. }
  87. $args = func_get_args();
  88. if (empty($args) || $action === null) {
  89. parent::allow();
  90. }
  91. parent::allow($args);
  92. }
  93. public function login($user = null) {
  94. $Model = $this->getModel();
  95. $this->_setDefaults();
  96. if (empty($user)) {
  97. $user = $this->identify($this->request, $this->response);
  98. }
  99. if (empty($user)) {
  100. $this->loginError = __('invalidLoginCredentials');
  101. return false;
  102. }
  103. # custom checks
  104. if (isset($user['active'])) {
  105. if (empty($user['active'])) {
  106. $this->loginError = __('Account not active yet');
  107. return false;
  108. }
  109. if (!empty($user['suspended'])) {
  110. $this->loginError = __('Account wurde vorübergehend gesperrt');
  111. if (!empty($user['suspended_reason'])) {
  112. $this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
  113. }
  114. return false;
  115. }
  116. } else {
  117. if (isset($user['status']) && empty($user['status'])) {
  118. $this->loginError = __('Account not active yet');
  119. return false;
  120. }
  121. if (isset($user['status']) && defined('User::STATUS_PENDING') && $user['status'] == User::STATUS_PENDING) {
  122. $this->loginError = __('Account wurde noch nicht freigeschalten');
  123. return false;
  124. }
  125. if (isset($user['status']) && defined('User::STATUS_SUSPENDED') && $user['status'] == User::STATUS_SUSPENDED) {
  126. $this->loginError = 'Account wurde vorübergehend gesperrt';
  127. if (!empty($user['suspended_reason'])) {
  128. $this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
  129. }
  130. return false;
  131. }
  132. if (isset($user['status']) && defined('User::STATUS_DEL') && $user['status'] == User::STATUS_DEL) {
  133. $this->loginError = 'Account wurde gelöscht';
  134. if (!empty($user['suspended_reason'])) {
  135. $this->loginError .= BR . BR . 'Grund:' . BR . nl2br(h($user['suspended_reason']));
  136. }
  137. return false;
  138. }
  139. if (isset($user['status']) && defined('User::STATUS_ACTIVE') && $user['status'] != User::STATUS_ACTIVE) {
  140. $this->loginError = __('Unknown Error');
  141. return false;
  142. }
  143. }
  144. if (isset($user['email_confirmed']) && empty($user['email_confirmed'])) {
  145. $this->loginError = __('Email not active yet');
  146. return false;
  147. }
  148. if ($user) {
  149. # update login counter
  150. if (isset($user['logins'])) {
  151. $user['logins'] = $user['logins'] + 1;
  152. if (method_exists($Model, 'loginUpdate')) {
  153. $Model->loginUpdate($user);
  154. }
  155. }
  156. $this->Session->renew();
  157. $this->Session->write(self::$sessionKey, $user);
  158. $this->Session->write(self::$sessionKey, $this->completeAuth($user));
  159. }
  160. return $this->loggedIn();
  161. }
  162. /**
  163. * return array $user or bool false on failure
  164. * 2011-11-03 ms
  165. */
  166. public function completeAuth($user) {
  167. $Model = $this->getModel();
  168. if (!is_array($user)) {
  169. $user = $Model->get($user);
  170. if (!$user) {
  171. return false;
  172. }
  173. $user = array_shift($user);
  174. }
  175. if (isset($Model->hasMany[$this->intermediateModel]['className'])) {
  176. $with = $Model->hasMany[$this->intermediateModel]['className'];
  177. } elseif (isset($Model->belongsTo[$this->roleModel]['className'])) {
  178. $with = $Model->belongsTo[$this->roleModel]['className'];
  179. }
  180. if (empty($with) && $this->settings['parentModelAlias'] !== false) {
  181. trigger_error('No relation from user to role found');
  182. return false;
  183. }
  184. $completeAuth = array($this->settings['userModel']=>$user);
  185. # roles
  186. if (!empty($with)) {
  187. list($plugin, $withModel) = pluginSplit($with);
  188. if (!isset($this->{$withModel})) {
  189. $this->{$withModel} = ClassRegistry::init($with);
  190. }
  191. # only for multi
  192. if ($this->settings['multi'] || !isset($completeAuth[$this->settings['userModel']]['role_id'])) {
  193. $parentModelAlias = $this->settings['parentModelAlias'];
  194. $completeAuth[$this->settings['userModel']][$parentModelAlias] = array(); # default: no roles!
  195. $roles = $this->{$withModel}->find('list', array('fields' => array($withModel.'.role_id'), 'conditions' => array($withModel.'.user_id' => $user['id'])));
  196. if (!empty($roles)) {
  197. //$primaryRole = $this->user($this->fieldKey);
  198. // retrieve associated role that are not the primary one
  199. # MAYBE USEFUL FOR GUEST!!!
  200. //$roles = set::extract('/'.$with.'['.$this->fieldKey.'!='.$primaryRole.']/'.$this->fieldKey, $roles);
  201. // add the suplemental roles id under the Auth session key
  202. $completeAuth[$this->settings['userModel']][$parentModelAlias] = $roles; // or USER_ROLE_KEY
  203. //pr($completeAuth);
  204. }
  205. } else {
  206. //$completeAuth[$this->settings['userModel']][$parentModelAlias][] = $completeAuth[$this->settings['userModel']]['role_id'];
  207. }
  208. }
  209. # deprecated!
  210. if (isset($Model->hasOne['UserInfo'])) {
  211. $with = $Model->hasOne['UserInfo']['className'];
  212. list($plugin, $withModel) = pluginSplit($with);
  213. if (!isset($this->{$withModel})) {
  214. $this->{$withModel} = ClassRegistry::init($with);
  215. }
  216. $infos = $this->{$withModel}->find('first', array('fields' => array(), 'conditions' => array($withModel.'.id' => $user['id'])));
  217. $completeAuth[$this->settings['userModel']][USER_INFO_KEY] = array(); # default: no rights!
  218. if (!empty($infos)) {
  219. $completeAuth[$this->settings['userModel']][USER_INFO_KEY] = $infos[$with];
  220. //pr($completeAuth);
  221. }
  222. }
  223. # deprecated!
  224. if (isset($Model->hasOne['UserRight'])) {
  225. $with = $Model->hasOne['UserRight']['className'];
  226. list($plugin, $withModel) = pluginSplit($with);
  227. if (!isset($this->{$withModel})) {
  228. $this->{$withModel} = ClassRegistry::init($with);
  229. }
  230. $rights = $this->{$withModel}->find('first', array('fields' => array(), 'conditions' => array($withModel.'.id' => $user['id'])));
  231. $completeAuth[$this->settings['userModel']][USER_RIGHT_KEY] = array(); # default: no rights!
  232. if (!empty($rights)) {
  233. // add the suplemental roles id under the Auth session key
  234. $completeAuth[$this->settings['userModel']][USER_RIGHT_KEY] = $rights[$with];
  235. //pr($completeAuth);
  236. }
  237. }
  238. if (method_exists($Model, 'completeAuth')) {
  239. $completeAuth = $Model->completeAuth($completeAuth);
  240. return $completeAuth[$this->settings['userModel']];
  241. }
  242. return $completeAuth[$this->settings['userModel']];
  243. }
  244. /**
  245. * Main execution method. Handles redirecting of invalid users, and processing
  246. * of login form data.
  247. *
  248. * @param Controller $controller A reference to the instantiating controller object
  249. * @return boolean
  250. */
  251. public function startup($controller) {
  252. if ($controller->name == 'CakeError') {
  253. return true;
  254. }
  255. $methods = array_flip(array_map('strtolower', $controller->methods));
  256. # fix: reverse camelCase first
  257. $action = strtolower(Inflector::underscore($controller->request->params['action']));
  258. $isMissingAction = (
  259. $controller->scaffold === false &&
  260. !isset($methods[$action])
  261. );
  262. if ($isMissingAction) {
  263. return true;
  264. }
  265. if (!$this->_setDefaults()) {
  266. return false;
  267. }
  268. $request = $controller->request;
  269. $url = '';
  270. if (isset($request->url)) {
  271. $url = $request->url;
  272. }
  273. $url = Router::normalize($url);
  274. $loginAction = Router::normalize($this->loginAction);
  275. $allowedActions = $this->allowedActions;
  276. $isAllowed = (
  277. $this->allowedActions == array('*') ||
  278. in_array($action, array_map('strtolower', $allowedActions))
  279. );
  280. if ($loginAction != $url && $isAllowed) {
  281. return true;
  282. }
  283. if ($loginAction == $url) {
  284. if (empty($request->data)) {
  285. if (!$this->Session->check('Auth.redirect') && !$this->loginRedirect && env('HTTP_REFERER')) {
  286. $this->Session->write('Auth.redirect', $controller->referer(null, true));
  287. }
  288. }
  289. return true;
  290. } else {
  291. if (!$this->_getUser()) {
  292. if (!$request->is('ajax')) {
  293. $this->flash($this->authError);
  294. $this->Session->write('Auth.redirect', Router::reverse($request));
  295. $controller->redirect($loginAction);
  296. return false;
  297. } elseif (!empty($this->ajaxLogin)) {
  298. $controller->viewPath = 'Elements';
  299. echo $controller->render($this->ajaxLogin, $this->RequestHandler->ajaxLayout);
  300. $this->_stop();
  301. return false;
  302. } else {
  303. $controller->redirect(null, 403);
  304. }
  305. }
  306. }
  307. if (empty($this->authorize) || $this->isAuthorized($this->user())) {
  308. return true;
  309. }
  310. $this->flash($this->authError);
  311. # redirect fix
  312. $controller->redirect($controller->referer($this->loginRedirect), null, true);
  313. }
  314. /**
  315. * Quickfix
  316. * TODO: improve - maybe use Authenticate
  317. * @return bool $success
  318. */
  319. public function verifyUser($id, $pwd) {
  320. $options = array(
  321. 'conditions' => array('id'=>$id, 'password'=>$this->password($pwd)),
  322. );
  323. return $this->getModel()->find('first', $options);
  324. $this->constructAuthenticate();
  325. $this->request->data['User']['password'] = $pwd;
  326. return $this->identify($this->request, $this->response);
  327. }
  328. /**
  329. * returns the current User model
  330. * @return object $User
  331. */
  332. public function getModel() {
  333. return ClassRegistry::init(CLASS_USER);
  334. }
  335. }