1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 0.10.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Controller\Component;
16:
17: use Cake\Auth\Storage\StorageInterface;
18: use Cake\Controller\Component;
19: use Cake\Controller\Controller;
20: use Cake\Core\App;
21: use Cake\Core\Exception\Exception;
22: use Cake\Event\Event;
23: use Cake\Event\EventDispatcherTrait;
24: use Cake\Http\Exception\ForbiddenException;
25: use Cake\Http\Response;
26: use Cake\Http\ServerRequest;
27: use Cake\Routing\Router;
28: use Cake\Utility\Hash;
29:
30: /**
31: * Authentication control component class.
32: *
33: * Binds access control with user authentication and session management.
34: *
35: * @property \Cake\Controller\Component\RequestHandlerComponent $RequestHandler
36: * @property \Cake\Controller\Component\FlashComponent $Flash
37: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html
38: */
39: class AuthComponent extends Component
40: {
41: use EventDispatcherTrait;
42:
43: /**
44: * The query string key used for remembering the referrered page when getting
45: * redirected to login.
46: */
47: const QUERY_STRING_REDIRECT = 'redirect';
48:
49: /**
50: * Constant for 'all'
51: *
52: * @var string
53: */
54: const ALL = 'all';
55:
56: /**
57: * Default config
58: *
59: * - `authenticate` - An array of authentication objects to use for authenticating users.
60: * You can configure multiple adapters and they will be checked sequentially
61: * when users are identified.
62: *
63: * ```
64: * $this->Auth->setConfig('authenticate', [
65: * 'Form' => [
66: * 'userModel' => 'Users.Users'
67: * ]
68: * ]);
69: * ```
70: *
71: * Using the class name without 'Authenticate' as the key, you can pass in an
72: * array of config for each authentication object. Additionally you can define
73: * config that should be set to all authentications objects using the 'all' key:
74: *
75: * ```
76: * $this->Auth->setConfig('authenticate', [
77: * AuthComponent::ALL => [
78: * 'userModel' => 'Users.Users',
79: * 'scope' => ['Users.active' => 1]
80: * ],
81: * 'Form',
82: * 'Basic'
83: * ]);
84: * ```
85: *
86: * - `authorize` - An array of authorization objects to use for authorizing users.
87: * You can configure multiple adapters and they will be checked sequentially
88: * when authorization checks are done.
89: *
90: * ```
91: * $this->Auth->setConfig('authorize', [
92: * 'Crud' => [
93: * 'actionPath' => 'controllers/'
94: * ]
95: * ]);
96: * ```
97: *
98: * Using the class name without 'Authorize' as the key, you can pass in an array
99: * of config for each authorization object. Additionally you can define config
100: * that should be set to all authorization objects using the AuthComponent::ALL key:
101: *
102: * ```
103: * $this->Auth->setConfig('authorize', [
104: * AuthComponent::ALL => [
105: * 'actionPath' => 'controllers/'
106: * ],
107: * 'Crud',
108: * 'CustomAuth'
109: * ]);
110: * ```
111: *
112: * - ~~`ajaxLogin`~~ - The name of an optional view element to render when an Ajax
113: * request is made with an invalid or expired session.
114: * **This option is deprecated since 3.3.6.** Your client side code should
115: * instead check for 403 status code and show appropriate login form.
116: *
117: * - `flash` - Settings to use when Auth needs to do a flash message with
118: * FlashComponent::set(). Available keys are:
119: *
120: * - `key` - The message domain to use for flashes generated by this component,
121: * defaults to 'auth'.
122: * - `element` - Flash element to use, defaults to 'default'.
123: * - `params` - The array of additional params to use, defaults to ['class' => 'error']
124: *
125: * - `loginAction` - A URL (defined as a string or array) to the controller action
126: * that handles logins. Defaults to `/users/login`.
127: *
128: * - `loginRedirect` - Normally, if a user is redirected to the `loginAction` page,
129: * the location they were redirected from will be stored in the session so that
130: * they can be redirected back after a successful login. If this session value
131: * is not set, redirectUrl() method will return the URL specified in `loginRedirect`.
132: *
133: * - `logoutRedirect` - The default action to redirect to after the user is logged out.
134: * While AuthComponent does not handle post-logout redirection, a redirect URL
135: * will be returned from `AuthComponent::logout()`. Defaults to `loginAction`.
136: *
137: * - `authError` - Error to display when user attempts to access an object or
138: * action to which they do not have access.
139: *
140: * - `unauthorizedRedirect` - Controls handling of unauthorized access.
141: *
142: * - For default value `true` unauthorized user is redirected to the referrer URL
143: * or `$loginRedirect` or '/'.
144: * - If set to a string or array the value is used as a URL to redirect to.
145: * - If set to false a `ForbiddenException` exception is thrown instead of redirecting.
146: *
147: * - `storage` - Storage class to use for persisting user record. When using
148: * stateless authenticator you should set this to 'Memory'. Defaults to 'Session'.
149: *
150: * - `checkAuthIn` - Name of event for which initial auth checks should be done.
151: * Defaults to 'Controller.startup'. You can set it to 'Controller.initialize'
152: * if you want the check to be done before controller's beforeFilter() is run.
153: *
154: * @var array
155: */
156: protected $_defaultConfig = [
157: 'authenticate' => null,
158: 'authorize' => null,
159: 'ajaxLogin' => null,
160: 'flash' => null,
161: 'loginAction' => null,
162: 'loginRedirect' => null,
163: 'logoutRedirect' => null,
164: 'authError' => null,
165: 'unauthorizedRedirect' => true,
166: 'storage' => 'Session',
167: 'checkAuthIn' => 'Controller.startup'
168: ];
169:
170: /**
171: * Other components utilized by AuthComponent
172: *
173: * @var array
174: */
175: public $components = ['RequestHandler', 'Flash'];
176:
177: /**
178: * Objects that will be used for authentication checks.
179: *
180: * @var \Cake\Auth\BaseAuthenticate[]
181: */
182: protected $_authenticateObjects = [];
183:
184: /**
185: * Objects that will be used for authorization checks.
186: *
187: * @var \Cake\Auth\BaseAuthorize[]
188: */
189: protected $_authorizeObjects = [];
190:
191: /**
192: * Storage object.
193: *
194: * @var \Cake\Auth\Storage\StorageInterface|null
195: */
196: protected $_storage;
197:
198: /**
199: * Controller actions for which user validation is not required.
200: *
201: * @var string[]
202: * @see \Cake\Controller\Component\AuthComponent::allow()
203: */
204: public $allowedActions = [];
205:
206: /**
207: * Request object
208: *
209: * @var \Cake\Http\ServerRequest
210: */
211: public $request;
212:
213: /**
214: * Response object
215: *
216: * @var \Cake\Http\Response
217: */
218: public $response;
219:
220: /**
221: * Instance of the Session object
222: *
223: * @var \Cake\Http\Session
224: * @deprecated 3.1.0 Will be removed in 4.0
225: */
226: public $session;
227:
228: /**
229: * The instance of the Authenticate provider that was used for
230: * successfully logging in the current user after calling `login()`
231: * in the same request
232: *
233: * @var \Cake\Auth\BaseAuthenticate|null
234: */
235: protected $_authenticationProvider;
236:
237: /**
238: * The instance of the Authorize provider that was used to grant
239: * access to the current user to the URL they are requesting.
240: *
241: * @var \Cake\Auth\BaseAuthorize|null
242: */
243: protected $_authorizationProvider;
244:
245: /**
246: * Initialize properties.
247: *
248: * @param array $config The config data.
249: * @return void
250: */
251: public function initialize(array $config)
252: {
253: $controller = $this->_registry->getController();
254: $this->setEventManager($controller->getEventManager());
255: $this->response =& $controller->response;
256: $this->session = $controller->getRequest()->getSession();
257:
258: if ($this->getConfig('ajaxLogin')) {
259: deprecationWarning(
260: 'The `ajaxLogin` option is deprecated. Your client-side ' .
261: 'code should instead check for 403 status code and show ' .
262: 'appropriate login form.'
263: );
264: }
265: }
266:
267: /**
268: * Callback for Controller.startup event.
269: *
270: * @param \Cake\Event\Event $event Event instance.
271: * @return \Cake\Http\Response|null
272: */
273: public function startup(Event $event)
274: {
275: return $this->authCheck($event);
276: }
277:
278: /**
279: * Main execution method, handles initial authentication check and redirection
280: * of invalid users.
281: *
282: * The auth check is done when event name is same as the one configured in
283: * `checkAuthIn` config.
284: *
285: * @param \Cake\Event\Event $event Event instance.
286: * @return \Cake\Http\Response|null
287: * @throws \ReflectionException
288: */
289: public function authCheck(Event $event)
290: {
291: if ($this->_config['checkAuthIn'] !== $event->getName()) {
292: return null;
293: }
294:
295: /** @var \Cake\Controller\Controller $controller */
296: $controller = $event->getSubject();
297:
298: $action = strtolower($controller->getRequest()->getParam('action'));
299: if (!$controller->isAction($action)) {
300: return null;
301: }
302:
303: $this->_setDefaults();
304:
305: if ($this->_isAllowed($controller)) {
306: return null;
307: }
308:
309: $isLoginAction = $this->_isLoginAction($controller);
310:
311: if (!$this->_getUser()) {
312: if ($isLoginAction) {
313: return null;
314: }
315: $result = $this->_unauthenticated($controller);
316: if ($result instanceof Response) {
317: $event->stopPropagation();
318: }
319:
320: return $result;
321: }
322:
323: if ($isLoginAction ||
324: empty($this->_config['authorize']) ||
325: $this->isAuthorized($this->user())
326: ) {
327: return null;
328: }
329:
330: $event->stopPropagation();
331:
332: return $this->_unauthorized($controller);
333: }
334:
335: /**
336: * Events supported by this component.
337: *
338: * @return array
339: */
340: public function implementedEvents()
341: {
342: return [
343: 'Controller.initialize' => 'authCheck',
344: 'Controller.startup' => 'startup',
345: ];
346: }
347:
348: /**
349: * Checks whether current action is accessible without authentication.
350: *
351: * @param \Cake\Controller\Controller $controller A reference to the instantiating
352: * controller object
353: * @return bool True if action is accessible without authentication else false
354: */
355: protected function _isAllowed(Controller $controller)
356: {
357: $action = strtolower($controller->getRequest()->getParam('action'));
358:
359: return in_array($action, array_map('strtolower', $this->allowedActions));
360: }
361:
362: /**
363: * Handles unauthenticated access attempt. First the `unauthenticated()` method
364: * of the last authenticator in the chain will be called. The authenticator can
365: * handle sending response or redirection as appropriate and return `true` to
366: * indicate no further action is necessary. If authenticator returns null this
367: * method redirects user to login action. If it's an AJAX request and config
368: * `ajaxLogin` is specified that element is rendered else a 403 HTTP status code
369: * is returned.
370: *
371: * @param \Cake\Controller\Controller $controller A reference to the controller object.
372: * @return \Cake\Http\Response|null Null if current action is login action
373: * else response object returned by authenticate object or Controller::redirect().
374: * @throws \Cake\Core\Exception\Exception
375: */
376: protected function _unauthenticated(Controller $controller)
377: {
378: if (empty($this->_authenticateObjects)) {
379: $this->constructAuthenticate();
380: }
381: $response = $this->response;
382: $auth = end($this->_authenticateObjects);
383: if ($auth === false) {
384: throw new Exception('At least one authenticate object must be available.');
385: }
386: $result = $auth->unauthenticated($controller->getRequest(), $response);
387: if ($result !== null) {
388: return $result;
389: }
390:
391: if (!$controller->getRequest()->is('ajax')) {
392: $this->flash($this->_config['authError']);
393:
394: return $controller->redirect($this->_loginActionRedirectUrl());
395: }
396:
397: if (!empty($this->_config['ajaxLogin'])) {
398: $controller->viewBuilder()->setTemplatePath('Element');
399: $response = $controller->render(
400: $this->_config['ajaxLogin'],
401: $this->RequestHandler->ajaxLayout
402: );
403: }
404:
405: return $response->withStatus(403);
406: }
407:
408: /**
409: * Returns the URL of the login action to redirect to.
410: *
411: * This includes the redirect query string if applicable.
412: *
413: * @return array|string
414: */
415: protected function _loginActionRedirectUrl()
416: {
417: $urlToRedirectBackTo = $this->_getUrlToRedirectBackTo();
418:
419: $loginAction = $this->_config['loginAction'];
420: if ($urlToRedirectBackTo === '/') {
421: return $loginAction;
422: }
423:
424: if (is_array($loginAction)) {
425: $loginAction['?'][static::QUERY_STRING_REDIRECT] = $urlToRedirectBackTo;
426: } else {
427: $char = strpos($loginAction, '?') === false ? '?' : '&';
428: $loginAction .= $char . static::QUERY_STRING_REDIRECT . '=' . urlencode($urlToRedirectBackTo);
429: }
430:
431: return $loginAction;
432: }
433:
434: /**
435: * Normalizes config `loginAction` and checks if current request URL is same as login action.
436: *
437: * @param \Cake\Controller\Controller $controller A reference to the controller object.
438: * @return bool True if current action is login action else false.
439: */
440: protected function _isLoginAction(Controller $controller)
441: {
442: $uri = $controller->request->getUri();
443: $url = Router::normalize($uri->getPath());
444: $loginAction = Router::normalize($this->_config['loginAction']);
445:
446: return $loginAction === $url;
447: }
448:
449: /**
450: * Handle unauthorized access attempt
451: *
452: * @param \Cake\Controller\Controller $controller A reference to the controller object
453: * @return \Cake\Http\Response
454: * @throws \Cake\Http\Exception\ForbiddenException
455: */
456: protected function _unauthorized(Controller $controller)
457: {
458: if ($this->_config['unauthorizedRedirect'] === false) {
459: throw new ForbiddenException($this->_config['authError']);
460: }
461:
462: $this->flash($this->_config['authError']);
463: if ($this->_config['unauthorizedRedirect'] === true) {
464: $default = '/';
465: if (!empty($this->_config['loginRedirect'])) {
466: $default = $this->_config['loginRedirect'];
467: }
468: if (is_array($default)) {
469: $default['_base'] = false;
470: }
471: $url = $controller->referer($default, true);
472: } else {
473: $url = $this->_config['unauthorizedRedirect'];
474: }
475:
476: return $controller->redirect($url);
477: }
478:
479: /**
480: * Sets defaults for configs.
481: *
482: * @return void
483: */
484: protected function _setDefaults()
485: {
486: $defaults = [
487: 'authenticate' => ['Form'],
488: 'flash' => [
489: 'element' => 'error',
490: 'key' => 'flash',
491: 'params' => ['class' => 'error']
492: ],
493: 'loginAction' => [
494: 'controller' => 'Users',
495: 'action' => 'login',
496: 'plugin' => null
497: ],
498: 'logoutRedirect' => $this->_config['loginAction'],
499: 'authError' => __d('cake', 'You are not authorized to access that location.')
500: ];
501:
502: $config = $this->getConfig();
503: foreach ($config as $key => $value) {
504: if ($value !== null) {
505: unset($defaults[$key]);
506: }
507: }
508: $this->setConfig($defaults);
509: }
510:
511: /**
512: * Check if the provided user is authorized for the request.
513: *
514: * Uses the configured Authorization adapters to check whether or not a user is authorized.
515: * Each adapter will be checked in sequence, if any of them return true, then the user will
516: * be authorized for the request.
517: *
518: * @param array|\ArrayAccess|null $user The user to check the authorization of.
519: * If empty the user fetched from storage will be used.
520: * @param \Cake\Http\ServerRequest|null $request The request to authenticate for.
521: * If empty, the current request will be used.
522: * @return bool True if $user is authorized, otherwise false
523: */
524: public function isAuthorized($user = null, ServerRequest $request = null)
525: {
526: if (empty($user) && !$this->user()) {
527: return false;
528: }
529: if (empty($user)) {
530: $user = $this->user();
531: }
532: if (empty($request)) {
533: $request = $this->getController()->getRequest();
534: }
535: if (empty($this->_authorizeObjects)) {
536: $this->constructAuthorize();
537: }
538: foreach ($this->_authorizeObjects as $authorizer) {
539: if ($authorizer->authorize($user, $request) === true) {
540: $this->_authorizationProvider = $authorizer;
541:
542: return true;
543: }
544: }
545:
546: return false;
547: }
548:
549: /**
550: * Loads the authorization objects configured.
551: *
552: * @return array|null The loaded authorization objects, or null when authorize is empty.
553: * @throws \Cake\Core\Exception\Exception
554: */
555: public function constructAuthorize()
556: {
557: if (empty($this->_config['authorize'])) {
558: return null;
559: }
560: $this->_authorizeObjects = [];
561: $authorize = Hash::normalize((array)$this->_config['authorize']);
562: $global = [];
563: if (isset($authorize[AuthComponent::ALL])) {
564: $global = $authorize[AuthComponent::ALL];
565: unset($authorize[AuthComponent::ALL]);
566: }
567: foreach ($authorize as $alias => $config) {
568: if (!empty($config['className'])) {
569: $class = $config['className'];
570: unset($config['className']);
571: } else {
572: $class = $alias;
573: }
574: $className = App::className($class, 'Auth', 'Authorize');
575: if (!class_exists($className)) {
576: throw new Exception(sprintf('Authorization adapter "%s" was not found.', $class));
577: }
578: if (!method_exists($className, 'authorize')) {
579: throw new Exception('Authorization objects must implement an authorize() method.');
580: }
581: $config = (array)$config + $global;
582: $this->_authorizeObjects[$alias] = new $className($this->_registry, $config);
583: }
584:
585: return $this->_authorizeObjects;
586: }
587:
588: /**
589: * Getter for authorize objects. Will return a particular authorize object.
590: *
591: * @param string $alias Alias for the authorize object
592: * @return \Cake\Auth\BaseAuthorize|null
593: */
594: public function getAuthorize($alias)
595: {
596: if (empty($this->_authorizeObjects)) {
597: $this->constructAuthorize();
598: }
599:
600: return isset($this->_authorizeObjects[$alias]) ? $this->_authorizeObjects[$alias] : null;
601: }
602:
603: /**
604: * Takes a list of actions in the current controller for which authentication is not required, or
605: * no parameters to allow all actions.
606: *
607: * You can use allow with either an array or a simple string.
608: *
609: * ```
610: * $this->Auth->allow('view');
611: * $this->Auth->allow(['edit', 'add']);
612: * ```
613: * or to allow all actions
614: * ```
615: * $this->Auth->allow();
616: * ```
617: *
618: * @param string|string[]|null $actions Controller action name or array of actions
619: * @return void
620: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html#making-actions-public
621: */
622: public function allow($actions = null)
623: {
624: if ($actions === null) {
625: $controller = $this->_registry->getController();
626: $this->allowedActions = get_class_methods($controller);
627:
628: return;
629: }
630: $this->allowedActions = array_merge($this->allowedActions, (array)$actions);
631: }
632:
633: /**
634: * Removes items from the list of allowed/no authentication required actions.
635: *
636: * You can use deny with either an array or a simple string.
637: *
638: * ```
639: * $this->Auth->deny('view');
640: * $this->Auth->deny(['edit', 'add']);
641: * ```
642: * or
643: * ```
644: * $this->Auth->deny();
645: * ```
646: * to remove all items from the allowed list
647: *
648: * @param string|string[]|null $actions Controller action name or array of actions
649: * @return void
650: * @see \Cake\Controller\Component\AuthComponent::allow()
651: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html#making-actions-require-authorization
652: */
653: public function deny($actions = null)
654: {
655: if ($actions === null) {
656: $this->allowedActions = [];
657:
658: return;
659: }
660: foreach ((array)$actions as $action) {
661: $i = array_search($action, $this->allowedActions, true);
662: if (is_int($i)) {
663: unset($this->allowedActions[$i]);
664: }
665: }
666: $this->allowedActions = array_values($this->allowedActions);
667: }
668:
669: /**
670: * Set provided user info to storage as logged in user.
671: *
672: * The storage class is configured using `storage` config key or passing
673: * instance to AuthComponent::storage().
674: *
675: * @param array|\ArrayAccess $user User data.
676: * @return void
677: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html#identifying-users-and-logging-them-in
678: */
679: public function setUser($user)
680: {
681: $this->storage()->write($user);
682: }
683:
684: /**
685: * Log a user out.
686: *
687: * Returns the logout action to redirect to. Triggers the `Auth.logout` event
688: * which the authenticate classes can listen for and perform custom logout logic.
689: *
690: * @return string Normalized config `logoutRedirect`
691: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html#logging-users-out
692: */
693: public function logout()
694: {
695: $this->_setDefaults();
696: if (empty($this->_authenticateObjects)) {
697: $this->constructAuthenticate();
698: }
699: $user = (array)$this->user();
700: $this->dispatchEvent('Auth.logout', [$user]);
701: $this->storage()->delete();
702:
703: return Router::normalize($this->_config['logoutRedirect']);
704: }
705:
706: /**
707: * Get the current user from storage.
708: *
709: * @param string|null $key Field to retrieve. Leave null to get entire User record.
710: * @return mixed|null Either User record or null if no user is logged in, or retrieved field if key is specified.
711: * @link https://book.cakephp.org/3.0/en/controllers/components/authentication.html#accessing-the-logged-in-user
712: */
713: public function user($key = null)
714: {
715: $user = $this->storage()->read();
716: if (!$user) {
717: return null;
718: }
719:
720: if ($key === null) {
721: return $user;
722: }
723:
724: return Hash::get($user, $key);
725: }
726:
727: /**
728: * Similar to AuthComponent::user() except if user is not found in
729: * configured storage, connected authentication objects will have their
730: * getUser() methods called.
731: *
732: * This lets stateless authentication methods function correctly.
733: *
734: * @return bool true If a user can be found, false if one cannot.
735: */
736: protected function _getUser()
737: {
738: $user = $this->user();
739: if ($user) {
740: return true;
741: }
742:
743: if (empty($this->_authenticateObjects)) {
744: $this->constructAuthenticate();
745: }
746: foreach ($this->_authenticateObjects as $auth) {
747: $result = $auth->getUser($this->getController()->getRequest());
748: if (!empty($result) && is_array($result)) {
749: $this->_authenticationProvider = $auth;
750: $event = $this->dispatchEvent('Auth.afterIdentify', [$result, $auth]);
751: if ($event->getResult() !== null) {
752: $result = $event->getResult();
753: }
754: $this->storage()->write($result);
755:
756: return true;
757: }
758: }
759:
760: return false;
761: }
762:
763: /**
764: * Get the URL a user should be redirected to upon login.
765: *
766: * Pass a URL in to set the destination a user should be redirected to upon
767: * logging in.
768: *
769: * If no parameter is passed, gets the authentication redirect URL. The URL
770: * returned is as per following rules:
771: *
772: * - Returns the normalized redirect URL from storage if it is
773: * present and for the same domain the current app is running on.
774: * - If there is no URL returned from storage and there is a config
775: * `loginRedirect`, the `loginRedirect` value is returned.
776: * - If there is no session and no `loginRedirect`, / is returned.
777: *
778: * @param string|array|null $url Optional URL to write as the login redirect URL.
779: * @return string Redirect URL
780: */
781: public function redirectUrl($url = null)
782: {
783: $redirectUrl = $this->getController()->getRequest()->getQuery(static::QUERY_STRING_REDIRECT);
784: if ($redirectUrl && (substr($redirectUrl, 0, 1) !== '/' || substr($redirectUrl, 0, 2) === '//')) {
785: $redirectUrl = null;
786: }
787:
788: if ($url !== null) {
789: $redirectUrl = $url;
790: } elseif ($redirectUrl) {
791: if (Router::normalize($redirectUrl) === Router::normalize($this->_config['loginAction'])) {
792: $redirectUrl = $this->_config['loginRedirect'];
793: }
794: } elseif ($this->_config['loginRedirect']) {
795: $redirectUrl = $this->_config['loginRedirect'];
796: } else {
797: $redirectUrl = '/';
798: }
799: if (is_array($redirectUrl)) {
800: return Router::url($redirectUrl + ['_base' => false]);
801: }
802:
803: return $redirectUrl;
804: }
805:
806: /**
807: * Use the configured authentication adapters, and attempt to identify the user
808: * by credentials contained in $request.
809: *
810: * Triggers `Auth.afterIdentify` event which the authenticate classes can listen
811: * to.
812: *
813: * @return array|bool User record data, or false, if the user could not be identified.
814: */
815: public function identify()
816: {
817: $this->_setDefaults();
818:
819: if (empty($this->_authenticateObjects)) {
820: $this->constructAuthenticate();
821: }
822: foreach ($this->_authenticateObjects as $auth) {
823: $result = $auth->authenticate($this->getController()->getRequest(), $this->response);
824: if (!empty($result)) {
825: $this->_authenticationProvider = $auth;
826: $event = $this->dispatchEvent('Auth.afterIdentify', [$result, $auth]);
827: if ($event->getResult() !== null) {
828: return $event->getResult();
829: }
830:
831: return $result;
832: }
833: }
834:
835: return false;
836: }
837:
838: /**
839: * Loads the configured authentication objects.
840: *
841: * @return array|null The loaded authorization objects, or null on empty authenticate value.
842: * @throws \Cake\Core\Exception\Exception
843: */
844: public function constructAuthenticate()
845: {
846: if (empty($this->_config['authenticate'])) {
847: return null;
848: }
849: $this->_authenticateObjects = [];
850: $authenticate = Hash::normalize((array)$this->_config['authenticate']);
851: $global = [];
852: if (isset($authenticate[AuthComponent::ALL])) {
853: $global = $authenticate[AuthComponent::ALL];
854: unset($authenticate[AuthComponent::ALL]);
855: }
856: foreach ($authenticate as $alias => $config) {
857: if (!empty($config['className'])) {
858: $class = $config['className'];
859: unset($config['className']);
860: } else {
861: $class = $alias;
862: }
863: $className = App::className($class, 'Auth', 'Authenticate');
864: if (!class_exists($className)) {
865: throw new Exception(sprintf('Authentication adapter "%s" was not found.', $class));
866: }
867: if (!method_exists($className, 'authenticate')) {
868: throw new Exception('Authentication objects must implement an authenticate() method.');
869: }
870: $config = array_merge($global, (array)$config);
871: $this->_authenticateObjects[$alias] = new $className($this->_registry, $config);
872: $this->getEventManager()->on($this->_authenticateObjects[$alias]);
873: }
874:
875: return $this->_authenticateObjects;
876: }
877:
878: /**
879: * Get/set user record storage object.
880: *
881: * @param \Cake\Auth\Storage\StorageInterface|null $storage Sets provided
882: * object as storage or if null returns configured storage object.
883: * @return \Cake\Auth\Storage\StorageInterface|null
884: */
885: public function storage(StorageInterface $storage = null)
886: {
887: if ($storage !== null) {
888: $this->_storage = $storage;
889:
890: return null;
891: }
892:
893: if ($this->_storage) {
894: return $this->_storage;
895: }
896:
897: $config = $this->_config['storage'];
898: if (is_string($config)) {
899: $class = $config;
900: $config = [];
901: } else {
902: $class = $config['className'];
903: unset($config['className']);
904: }
905: $className = App::className($class, 'Auth/Storage', 'Storage');
906: if (!class_exists($className)) {
907: throw new Exception(sprintf('Auth storage adapter "%s" was not found.', $class));
908: }
909: $request = $this->getController()->getRequest();
910: $response = $this->getController()->getResponse();
911: $this->_storage = new $className($request, $response, $config);
912:
913: return $this->_storage;
914: }
915:
916: /**
917: * Magic accessor for backward compatibility for property `$sessionKey`.
918: *
919: * @param string $name Property name
920: * @return mixed
921: */
922: public function __get($name)
923: {
924: if ($name === 'sessionKey') {
925: return $this->storage()->getConfig('key');
926: }
927:
928: return parent::__get($name);
929: }
930:
931: /**
932: * Magic setter for backward compatibility for property `$sessionKey`.
933: *
934: * @param string $name Property name.
935: * @param mixed $value Value to set.
936: * @return void
937: */
938: public function __set($name, $value)
939: {
940: if ($name === 'sessionKey') {
941: $this->_storage = null;
942:
943: if ($value === false) {
944: $this->setConfig('storage', 'Memory');
945:
946: return;
947: }
948:
949: $this->setConfig('storage', 'Session');
950: $this->storage()->setConfig('key', $value);
951:
952: return;
953: }
954:
955: $this->{$name} = $value;
956: }
957:
958: /**
959: * Getter for authenticate objects. Will return a particular authenticate object.
960: *
961: * @param string $alias Alias for the authenticate object
962: *
963: * @return \Cake\Auth\BaseAuthenticate|null
964: */
965: public function getAuthenticate($alias)
966: {
967: if (empty($this->_authenticateObjects)) {
968: $this->constructAuthenticate();
969: }
970:
971: return isset($this->_authenticateObjects[$alias]) ? $this->_authenticateObjects[$alias] : null;
972: }
973:
974: /**
975: * Set a flash message. Uses the Flash component with values from `flash` config.
976: *
977: * @param string $message The message to set.
978: * @return void
979: */
980: public function flash($message)
981: {
982: if ($message === false) {
983: return;
984: }
985:
986: $this->Flash->set($message, $this->_config['flash']);
987: }
988:
989: /**
990: * If login was called during this request and the user was successfully
991: * authenticated, this function will return the instance of the authentication
992: * object that was used for logging the user in.
993: *
994: * @return \Cake\Auth\BaseAuthenticate|null
995: */
996: public function authenticationProvider()
997: {
998: return $this->_authenticationProvider;
999: }
1000:
1001: /**
1002: * If there was any authorization processing for the current request, this function
1003: * will return the instance of the Authorization object that granted access to the
1004: * user to the current address.
1005: *
1006: * @return \Cake\Auth\BaseAuthorize|null
1007: */
1008: public function authorizationProvider()
1009: {
1010: return $this->_authorizationProvider;
1011: }
1012:
1013: /**
1014: * Returns the URL to redirect back to or / if not possible.
1015: *
1016: * This method takes the referrer into account if the
1017: * request is not of type GET.
1018: *
1019: * @return string
1020: */
1021: protected function _getUrlToRedirectBackTo()
1022: {
1023: $urlToRedirectBackTo = $this->request->getRequestTarget();
1024: if (!$this->request->is('get')) {
1025: $urlToRedirectBackTo = $this->request->referer(true);
1026: }
1027:
1028: return $urlToRedirectBackTo;
1029: }
1030: }
1031: