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 15App::uses('Security', 'Utility'); 16App::uses('Hash', 'Utility'); 17App::uses('CakeEventListener', 'Event'); 18 19/** 20 * Base Authentication class with common methods and properties. 21 * 22 * @package Cake.Controller.Component.Auth 23 */ 24abstract class BaseAuthenticate implements CakeEventListener { 25 26/** 27 * Settings for this object. 28 * 29 * - `fields` The fields to use to identify a user by. 30 * - `userModel` The model name of the User, defaults to User. 31 * - `userFields` Array of fields to retrieve from User model, null to retrieve all. Defaults to null. 32 * - `scope` Additional conditions to use when looking up and authenticating users, 33 * i.e. `array('User.is_active' => 1).` 34 * - `recursive` The value of the recursive key passed to find(). Defaults to 0. 35 * - `contain` Extra models to contain and store in session. 36 * - `passwordHasher` Password hasher class. Can be a string specifying class name 37 * or an array containing `className` key, any other keys will be passed as 38 * settings to the class. Defaults to 'Simple'. 39 * 40 * @var array 41 */ 42 public $settings = array( 43 'fields' => array( 44 'username' => 'username', 45 'password' => 'password' 46 ), 47 'userModel' => 'User', 48 'userFields' => null, 49 'scope' => array(), 50 'recursive' => 0, 51 'contain' => null, 52 'passwordHasher' => 'Simple' 53 ); 54 55/** 56 * A Component collection, used to get more components. 57 * 58 * @var ComponentCollection 59 */ 60 protected $_Collection; 61 62/** 63 * Password hasher instance. 64 * 65 * @var AbstractPasswordHasher 66 */ 67 protected $_passwordHasher; 68 69/** 70 * Implemented events 71 * 72 * @return array of events => callbacks. 73 */ 74 public function implementedEvents() { 75 return array(); 76 } 77 78/** 79 * Constructor 80 * 81 * @param ComponentCollection $collection The Component collection used on this request. 82 * @param array $settings Array of settings to use. 83 */ 84 public function __construct(ComponentCollection $collection, $settings) { 85 $this->_Collection = $collection; 86 $this->settings = Hash::merge($this->settings, $settings); 87 } 88 89/** 90 * Find a user record using the standard options. 91 * 92 * The $username parameter can be a (string)username or an array containing 93 * conditions for Model::find('first'). If the $password param is not provided 94 * the password field will be present in returned array. 95 * 96 * Input passwords will be hashed even when a user doesn't exist. This 97 * helps mitigate timing attacks that are attempting to find valid usernames. 98 * 99 * @param string|array $username The username/identifier, or an array of find conditions. 100 * @param string $password The password, only used if $username param is string. 101 * @return bool|array Either false on failure, or an array of user data. 102 */ 103 protected function _findUser($username, $password = null) { 104 $userModel = $this->settings['userModel']; 105 list(, $model) = pluginSplit($userModel); 106 $fields = $this->settings['fields']; 107 108 if (is_array($username)) { 109 $conditions = $username; 110 } else { 111 $conditions = array( 112 $model . '.' . $fields['username'] => $username 113 ); 114 } 115 116 if (!empty($this->settings['scope'])) { 117 $conditions = array_merge($conditions, $this->settings['scope']); 118 } 119 120 $userFields = $this->settings['userFields']; 121 if ($password !== null && $userFields !== null) { 122 $userFields[] = $model . '.' . $fields['password']; 123 } 124 125 $result = ClassRegistry::init($userModel)->find('first', array( 126 'conditions' => $conditions, 127 'recursive' => $this->settings['recursive'], 128 'fields' => $userFields, 129 'contain' => $this->settings['contain'], 130 )); 131 if (empty($result[$model])) { 132 $this->passwordHasher()->hash($password); 133 return false; 134 } 135 136 $user = $result[$model]; 137 if ($password !== null) { 138 if (!$this->passwordHasher()->check($password, $user[$fields['password']])) { 139 return false; 140 } 141 unset($user[$fields['password']]); 142 } 143 144 unset($result[$model]); 145 return array_merge($user, $result); 146 } 147 148/** 149 * Return password hasher object 150 * 151 * @return AbstractPasswordHasher Password hasher instance 152 * @throws CakeException If password hasher class not found or 153 * it does not extend AbstractPasswordHasher 154 */ 155 public function passwordHasher() { 156 if ($this->_passwordHasher) { 157 return $this->_passwordHasher; 158 } 159 160 $config = array(); 161 if (is_string($this->settings['passwordHasher'])) { 162 $class = $this->settings['passwordHasher']; 163 } else { 164 $class = $this->settings['passwordHasher']['className']; 165 $config = $this->settings['passwordHasher']; 166 unset($config['className']); 167 } 168 list($plugin, $class) = pluginSplit($class, true); 169 $className = $class . 'PasswordHasher'; 170 App::uses($className, $plugin . 'Controller/Component/Auth'); 171 if (!class_exists($className)) { 172 throw new CakeException(__d('cake_dev', 'Password hasher class "%s" was not found.', $class)); 173 } 174 if (!is_subclass_of($className, 'AbstractPasswordHasher')) { 175 throw new CakeException(__d('cake_dev', 'Password hasher must extend AbstractPasswordHasher class.')); 176 } 177 $this->_passwordHasher = new $className($config); 178 return $this->_passwordHasher; 179 } 180 181/** 182 * Hash the plain text password so that it matches the hashed/encrypted password 183 * in the datasource. 184 * 185 * @param string $password The plain text password. 186 * @return string The hashed form of the password. 187 * @deprecated 3.0.0 Since 2.4. Use a PasswordHasher class instead. 188 */ 189 protected function _password($password) { 190 return Security::hash($password, null, true); 191 } 192 193/** 194 * Authenticate a user based on the request information. 195 * 196 * @param CakeRequest $request Request to get authentication information from. 197 * @param CakeResponse $response A response object that can have headers added. 198 * @return mixed Either false on failure, or an array of user data on success. 199 */ 200 abstract public function authenticate(CakeRequest $request, CakeResponse $response); 201 202/** 203 * Allows you to hook into AuthComponent::logout(), 204 * and implement specialized logout behavior. 205 * 206 * All attached authentication objects will have this method 207 * called when a user logs out. 208 * 209 * @param array $user The user about to be logged out. 210 * @return void 211 */ 212 public function logout($user) { 213 } 214 215/** 216 * Get a user based on information in the request. Primarily used by stateless authentication 217 * systems like basic and digest auth. 218 * 219 * @param CakeRequest $request Request object. 220 * @return mixed Either false or an array of user information 221 */ 222 public function getUser(CakeRequest $request) { 223 return false; 224 } 225 226/** 227 * Handle unauthenticated access attempt. 228 * 229 * @param CakeRequest $request A request object. 230 * @param CakeResponse $response A response object. 231 * @return mixed Either true to indicate the unauthenticated request has been 232 * dealt with and no more action is required by AuthComponent or void (default). 233 */ 234 public function unauthenticated(CakeRequest $request, CakeResponse $response) { 235 } 236 237} 238