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: * @license https://opensource.org/licenses/mit-license.php MIT License
13: */
14: namespace Cake\Auth;
15:
16: use Cake\Controller\ComponentRegistry;
17: use Cake\Core\InstanceConfigTrait;
18: use Cake\Event\EventListenerInterface;
19: use Cake\Http\Response;
20: use Cake\Http\ServerRequest;
21: use Cake\ORM\Locator\LocatorAwareTrait;
22:
23: /**
24: * Base Authentication class with common methods and properties.
25: */
26: abstract class BaseAuthenticate implements EventListenerInterface
27: {
28: use InstanceConfigTrait;
29: use LocatorAwareTrait;
30:
31: /**
32: * Default config for this object.
33: *
34: * - `fields` The fields to use to identify a user by.
35: * - `userModel` The alias for users table, defaults to Users.
36: * - `finder` The finder method to use to fetch user record. Defaults to 'all'.
37: * You can set finder name as string or an array where key is finder name and value
38: * is an array passed to `Table::find()` options.
39: * E.g. ['finderName' => ['some_finder_option' => 'some_value']]
40: * - `passwordHasher` Password hasher class. Can be a string specifying class name
41: * or an array containing `className` key, any other keys will be passed as
42: * config to the class. Defaults to 'Default'.
43: * - Options `scope` and `contain` have been deprecated since 3.1. Use custom
44: * finder instead to modify the query to fetch user record.
45: *
46: * @var array
47: */
48: protected $_defaultConfig = [
49: 'fields' => [
50: 'username' => 'username',
51: 'password' => 'password'
52: ],
53: 'userModel' => 'Users',
54: 'scope' => [],
55: 'finder' => 'all',
56: 'contain' => null,
57: 'passwordHasher' => 'Default'
58: ];
59:
60: /**
61: * A Component registry, used to get more components.
62: *
63: * @var \Cake\Controller\ComponentRegistry
64: */
65: protected $_registry;
66:
67: /**
68: * Password hasher instance.
69: *
70: * @var \Cake\Auth\AbstractPasswordHasher
71: */
72: protected $_passwordHasher;
73:
74: /**
75: * Whether or not the user authenticated by this class
76: * requires their password to be rehashed with another algorithm.
77: *
78: * @var bool
79: */
80: protected $_needsPasswordRehash = false;
81:
82: /**
83: * Constructor
84: *
85: * @param \Cake\Controller\ComponentRegistry $registry The Component registry used on this request.
86: * @param array $config Array of config to use.
87: */
88: public function __construct(ComponentRegistry $registry, array $config = [])
89: {
90: $this->_registry = $registry;
91: $this->setConfig($config);
92:
93: if ($this->getConfig('scope') || $this->getConfig('contain')) {
94: deprecationWarning(
95: 'The `scope` and `contain` options for Authentication are deprecated. ' .
96: 'Use the `finder` option instead to define additional conditions.'
97: );
98: }
99: }
100:
101: /**
102: * Find a user record using the username and password provided.
103: *
104: * Input passwords will be hashed even when a user doesn't exist. This
105: * helps mitigate timing attacks that are attempting to find valid usernames.
106: *
107: * @param string $username The username/identifier.
108: * @param string|null $password The password, if not provided password checking is skipped
109: * and result of find is returned.
110: * @return bool|array Either false on failure, or an array of user data.
111: */
112: protected function _findUser($username, $password = null)
113: {
114: $result = $this->_query($username)->first();
115:
116: if (empty($result)) {
117: // Waste time hashing the password, to prevent
118: // timing side-channels. However, don't hash
119: // null passwords as authentication systems
120: // like digest auth don't use passwords
121: // and hashing *could* create a timing side-channel.
122: if ($password !== null) {
123: $hasher = $this->passwordHasher();
124: $hasher->hash($password);
125: }
126:
127: return false;
128: }
129:
130: $passwordField = $this->_config['fields']['password'];
131: if ($password !== null) {
132: $hasher = $this->passwordHasher();
133: $hashedPassword = $result->get($passwordField);
134: if (!$hasher->check($password, $hashedPassword)) {
135: return false;
136: }
137:
138: $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
139: $result->unsetProperty($passwordField);
140: }
141: $hidden = $result->getHidden();
142: if ($password === null && in_array($passwordField, $hidden)) {
143: $key = array_search($passwordField, $hidden);
144: unset($hidden[$key]);
145: $result->setHidden($hidden);
146: }
147:
148: return $result->toArray();
149: }
150:
151: /**
152: * Get query object for fetching user from database.
153: *
154: * @param string $username The username/identifier.
155: * @return \Cake\ORM\Query
156: */
157: protected function _query($username)
158: {
159: $config = $this->_config;
160: $table = $this->getTableLocator()->get($config['userModel']);
161:
162: $options = [
163: 'conditions' => [$table->aliasField($config['fields']['username']) => $username]
164: ];
165:
166: if (!empty($config['scope'])) {
167: $options['conditions'] = array_merge($options['conditions'], $config['scope']);
168: }
169: if (!empty($config['contain'])) {
170: $options['contain'] = $config['contain'];
171: }
172:
173: $finder = $config['finder'];
174: if (is_array($finder)) {
175: $options += current($finder);
176: $finder = key($finder);
177: }
178:
179: if (!isset($options['username'])) {
180: $options['username'] = $username;
181: }
182:
183: return $table->find($finder, $options);
184: }
185:
186: /**
187: * Return password hasher object
188: *
189: * @return \Cake\Auth\AbstractPasswordHasher Password hasher instance
190: * @throws \RuntimeException If password hasher class not found or
191: * it does not extend AbstractPasswordHasher
192: */
193: public function passwordHasher()
194: {
195: if ($this->_passwordHasher) {
196: return $this->_passwordHasher;
197: }
198:
199: $passwordHasher = $this->_config['passwordHasher'];
200:
201: return $this->_passwordHasher = PasswordHasherFactory::build($passwordHasher);
202: }
203:
204: /**
205: * Returns whether or not the password stored in the repository for the logged in user
206: * requires to be rehashed with another algorithm
207: *
208: * @return bool
209: */
210: public function needsPasswordRehash()
211: {
212: return $this->_needsPasswordRehash;
213: }
214:
215: /**
216: * Authenticate a user based on the request information.
217: *
218: * @param \Cake\Http\ServerRequest $request Request to get authentication information from.
219: * @param \Cake\Http\Response $response A response object that can have headers added.
220: * @return mixed Either false on failure, or an array of user data on success.
221: */
222: abstract public function authenticate(ServerRequest $request, Response $response);
223:
224: /**
225: * Get a user based on information in the request. Primarily used by stateless authentication
226: * systems like basic and digest auth.
227: *
228: * @param \Cake\Http\ServerRequest $request Request object.
229: * @return mixed Either false or an array of user information
230: */
231: public function getUser(ServerRequest $request)
232: {
233: return false;
234: }
235:
236: /**
237: * Handle unauthenticated access attempt. In implementation valid return values
238: * can be:
239: *
240: * - Null - No action taken, AuthComponent should return appropriate response.
241: * - Cake\Http\Response - A response object, which will cause AuthComponent to
242: * simply return that response.
243: *
244: * @param \Cake\Http\ServerRequest $request A request object.
245: * @param \Cake\Http\Response $response A response object.
246: * @return void
247: */
248: public function unauthenticated(ServerRequest $request, Response $response)
249: {
250: }
251:
252: /**
253: * Returns a list of all events that this authenticate class will listen to.
254: *
255: * An authenticate class can listen to following events fired by AuthComponent:
256: *
257: * - `Auth.afterIdentify` - Fired after a user has been identified using one of
258: * configured authenticate class. The callback function should have signature
259: * like `afterIdentify(Event $event, array $user)` when `$user` is the
260: * identified user record.
261: *
262: * - `Auth.logout` - Fired when AuthComponent::logout() is called. The callback
263: * function should have signature like `logout(Event $event, array $user)`
264: * where `$user` is the user about to be logged out.
265: *
266: * @return array List of events this class listens to. Defaults to `[]`.
267: */
268: public function implementedEvents()
269: {
270: return [];
271: }
272: }
273: