[ * 'adapter' => 'Form', * 'model' => 'Customers', * 'fields' => ['email', 'password'] * ] * ]); * ``` * * If the field names present in the form match the fields used in the database lookup, the above * will suffice. If, however, the form fields must be matched to different database field names, * you can specify an array which matches up the form field names to their corresponding database * field names. Suppose, for example, user authentication information in a MongoDB database is * nested within a sub-object called `login`. The adapter could be configured as follows: * * ``` * Auth::config([ * 'customer' => [ * 'adapter' => 'Form', * 'model' => 'Customers', * 'fields' => ['username' => 'login.username', 'password' => 'login.password'], * 'scope' => ['active' => true] * ] * ]); * ``` * * Note that any additional fields may be specified which should be included in the query. For * example, if a user must select a group when logging in, you may override the `'fields'` key with * that value as well (i.e. `'fields' => ['username', 'password', 'group']`). If a field is * specified which is not present in the request data, the value in the authentication query will be * `null`). Note that this will only submit data that is specified in the incoming request. If you * would like to further limit the query using fixed conditions, use the `'scope'` key, as shown in * the example above. * * ## Pre-Query Filtering * * As mentioned, prior to any queries being executed, the request data is modified by any filters * configured. Filters are callbacks which accept the value of a submitted form field as input, and * return a modified version of the value as output. Filters can be any PHP callable, i.e. a closure * or `array('ClassName', 'method')`. * * For example, if you're doing simple password hashing against a legacy application, you can * configure the adapter as follows: * * ``` * Auth::config([ * 'default' => [ * 'adapter' => 'Form', * 'filters' => ['password' => ['lithium\security\Hash', 'calculate']], * 'validators' => ['password' => false] * ] * ]); * ``` * * This applies the default system hash (SHA 512) against the password prior to using it in the * query, and overrides `'validators'` to disable the default crypto-based query validation that * would occur after the query. * * Note that if you are specifying the `'fields'` configuration using key / value pairs, the key * used to specify the filter must match the key side of the `'fields'` assignment. Additionally, * specifying a filter with no key allows the entire data array to be filtered, as in the following: * * ``` * Auth::config([ * 'default' => [ * 'adapter' => 'Form', * 'filters' => [function ($data) { * // Make any modifications to $data, including adding/removing keys * return $data; * }] * ] * ]); * ``` * * For more information, see the `_filters()` method or the `$_filters` property. * * ## Post-Query Validation * * In addition to filtering data, you can also apply validators to do check submitted form data * against database values programmatically. For example, the default adapter uses a cryptographic * hash function which operates in constant time to validate passwords. Configuring this validator * manually would work as follows: * * ``` * use lithium\security\Password; * * Auth::config([ * 'default' => [ * 'adapter' => 'Form', * 'validators' => [ * 'password' => function($form, $data) { * return Password::check($form, $data); * } * ] * ] * ]); * ``` * * As with filters, each validator can be defined as any PHP callable, and must be keyed using the * name of the form field submitted (if form and database field names do not match). If a validator * is specified with no key, it will apply to all data submitted. See the `$_validators` property * and the `_validate()` method for more information. * * @see lithium\net\http\Request::$data * @see lithium\data\Model::find() * @see lithium\security\Hash::calculate() */ class Form extends \lithium\core\ObjectDeprecated { /** * The name of the model class to query against. This can either be a model name (i.e. * `'Users'`), or a fully-namespaced class reference (i.e. `'app\models\Users'`). When * authenticating users, the magic `first()` method is invoked against the model to return the * first record found when combining the conditions in the `$_scope` property with the * authentication data yielded from the `Request` object in `Form::check()`. (Note that the * model method called is configurable using the `$_query` property). * * @see lithium\security\auth\adapter\Form::$_query * @var string */ protected $_model = ''; /** * The list of fields to extract from the `Request` object and use when querying the database. * This can either be a simple array of field names, or a set of key/value pairs, which map * the field names in the request to database field names. * * For example, if you had a form field name `username`, which mapped to a database field named * username, you could use the following in the `'fields'` configuration: * * ``` embed:lithium\tests\cases\security\auth\adapter\FormTest::testMixedFieldMapping(3-3) ``` * * This is especially relevant for document databases, where you may want to map a form field to * a nested document field: * * ``` * 'fields' => ['username' => 'login.username', 'password'], * ``` * * @var array */ protected $_fields = []; /** * Additional data to apply to the model query conditions when looking up users, i.e. * `array('active' => true)` to disallow authenticating against inactive user accounts. * * @var array */ protected $_scope = []; /** * Callback filters to apply to request data before using it in the authentication query. Each * key in the array must match a request field specified in the `$_fields` property, and each * value must either be a reference to a function or method name, or a closure. For example, to * automatically hash passwords using simple SHA 512 hashing, the `Form` adapter could be * configured with the following: * ``` * ['password' => ['lithium\security\Hash', 'calculate']] * ``` * * Optionally, you can specify a callback with no key, which will receive (and can modify) the * entire credentials array before the query is executed, as in the following example: * * ``` * Auth::config([ * 'members' => [ * 'adapter' => 'Form', * 'model' => 'Member', * 'fields' => ['email', 'password'], * 'filters' => [function($data) { * // If the user is outside the company, then their account must have the * // 'private' field set to true in order to log in: * if (!preg_match('/@mycompany\.com$/', $data['email'])) { * $data['private'] = true; * } * return $data; * }] * ] * ]); * ``` * * @see lithium\security\auth\adapter\Form::$_fields * @var array */ protected $_filters = []; /** * An array of callbacks, keyed by form field name, which make an assertion about a piece of * submitted form data. Each validator should accept the value of the form field submitted * (which will be modified by any applied filters), and return a boolean value indicating the * success of the validation. If a validator is specified with no key, it will receive all form * data and all database data. See the `_validate()` method for more information. * * @see lithium\security\auth\adapter\Form::_validate() * @var array */ protected $_validators = []; /** * If you require custom model logic in your authentication query, use this setting to specify * which model method to call, and this method will receive the authentication query. In return, * the `Form` adapter expects a `Record` object which implements the `data()` method. See the * constructor for more information on setting this property. Defaults to `'first'`, which * calls, for example, `Users::first()`. * * @see lithium\security\auth\adapter\Form::__construct() * @see lithium\data\entity\Record::data() * @var string */ protected $_query = 'first'; /** * List of configuration properties to automatically assign to the properties of the adapter * when the class is constructed. * * @var array */ protected $_autoConfig = ['model', 'fields', 'scope', 'filters', 'validators', 'query']; /** * Constructor. Sets the initial configuration for the `Form` adapter, as detailed below. * * @see lithium\security\auth\adapter\Form::$_model * @see lithium\security\auth\adapter\Form::$_fields * @see lithium\security\auth\adapter\Form::$_filters * @see lithium\security\auth\adapter\Form::$_validators * @see lithium\security\auth\adapter\Form::$_query * @param array $config Available configuration options: * `'model'` _string_: The name of the model class to use. See the `$_model` * property for details. * `'fields'` _array_: The model fields to query against when taking input from * the request data. See the `$_fields` property for details. * `'scope'` _array_: Any additional conditions used to constrain the * authentication query. For example, if active accounts in an application have * an `active` field which must be set to `true`, you can specify * `'scope' => ['active' => true]`. See the `$_scope` property for more * details. * `'filters'` _array_: Named callbacks to apply to request data before the user * lookup query is generated. See the `$_filters` property for more details. * `'validators'` _array_: Named callbacks to apply to fields in request data and * corresponding fields in database data in order to do programmatic * authentication checks after the query has occurred. See the `$_validators` * property for more details. * `'query'` _string_: Determines the model method to invoke for authentication * checks. See the `$_query` property for more details. * @return void */ public function __construct(array $config = []) { $defaults = [ 'model' => 'Users', 'query' => 'first', 'filters' => [], 'validators' => [], 'fields' => ['username', 'password'] ]; $config += $defaults; $password = function($form, $data) { return Password::check($form, $data); }; $config['validators'] = array_filter($config['validators'] + compact('password')); parent::__construct($config + $defaults); } /** * Initializes values configured in the constructor. * * @return void */ protected function _init() { parent::_init(); foreach ($this->_fields as $key => $val) { if (is_int($key)) { unset($this->_fields[$key]); $this->_fields[$val] = $val; } } if (!class_exists($model = Libraries::locate('models', $this->_model))) { throw new ClassNotFoundException("Model class '{$this->_model}' not found."); } $this->_model = $model; } /** * Called by the `Auth` class to run an authentication check against a model class using the * credentials in a data container (a `Request` object), and returns an array of user * information on success, or `false` on failure. * * @param object $credentials A data container which wraps the authentication credentials used * to query the model (usually a `Request` object). See the documentation for this * class for further details. * @param array $options Additional configuration options. Not currently implemented in this * adapter. * @return array Returns an array containing user information on success, or `false` on failure. */ public function check($credentials, array $options = []) { $model = $this->_model; $query = $this->_query; $data = $this->_filters($this->_data($credentials->data)); $validate = array_flip(array_intersect_key($this->_fields, $this->_validators)); $conditions = $this->_scope + array_diff_key($data, $validate); $user = $model::$query(compact('conditions') + $options); if (!$user) { return false; } return $this->_validate($user, $data); } /** * A pass-through method called by `Auth`. Returns the value of `$data`, which is written to * a user's session. When implementing a custom adapter, this method may be used to modify or * reject data before it is written to the session. * * @param array $data User data to be written to the session. * @param array $options Adapter-specific options. Not implemented in the `Form` adapter. * @return array Returns the value of `$data`. */ public function set($data, array $options = []) { return $data; } /** * Called by `Auth` when a user session is terminated. Not implemented in the `Form` adapter. * * @param array $options Adapter-specific options. Not implemented in the `Form` adapter. * @return void */ public function clear(array $options = []) {} /** * Iterates over the filters configured in `$_filters` which are applied to submitted form data * _before_ it is used in the query. * * @see lithium\security\auth\adapter\Form::$_filters * @param array $data The array of raw form data to be filtered. * @return array Callback result. */ protected function _filters($data) { $result = []; foreach ($this->_fields as $from => $to) { $result[$to] = isset($data[$from]) ? $data[$from] : null; if (!isset($this->_filters[$from])) { $result[$to] = is_scalar($result[$to]) ? $result[$to] : null; continue; } if ($this->_filters[$from] === false) { continue; } if (!is_callable($this->_filters[$from])) { $message = "Authentication filter for `{$from}` is not callable."; throw new UnexpectedValueException($message); } $result[$to] = call_user_func($this->_filters[$from], $result[$to]); } if (!isset($this->_filters[0])) { return $result; } if (!is_callable($this->_filters[0])) { throw new UnexpectedValueException("Authentication filter is not callable."); } return call_user_func($this->_filters[0], $result); } /** * After an authentication query against the configured model class has occurred, this method * iterates over the configured validators and checks each one by passing the submitted form * value as the first parameter, and the corresponding database value as the second. The * validator then returns a boolean to indicate success. If the validator fails, it will cause * the entire authentication operation to fail. Note that any filters applied to a form field * will affect the data passed to the validator. * * @see lithium\security\auth\adapter\Form::__construct() * @see lithium\security\auth\adapter\Form::$_validators * @param object $user The user object returned from the database. This object must support a * `data()` method, which returns the object's array representation, and * also returns individual field values by name. * @param array $data The form data submitted in the request and passed to `Form::check()`. * @return array Returns an array of authenticated user data on success, otherwise `false` if * any of the configured validators fails. See `'validators'` in the `$config` * parameter of `__construct()`. */ protected function _validate($user, array $data) { foreach ($this->_validators as $field => $validator) { if (!isset($this->_fields[$field]) || $field === 0) { continue; } if (!is_callable($validator)) { $message = "Authentication validator for `{$field}` is not callable."; throw new UnexpectedValueException($message); } $field = $this->_fields[$field]; $value = isset($data[$field]) ? $data[$field] : null; if (!call_user_func($validator, $value, $user->data($field))) { return false; } } $user = $user->data(); if (!isset($this->_validators[0])) { return $user; } if (!is_callable($this->_validators[0])) { throw new UnexpectedValueException("Authentication validator is not callable."); } return call_user_func($this->_validators[0], $data, $user) ? $user : false; } /** * Checks if the data container values are inside indexed arrays from binding. * Get the values from the binding coresponding to the model if such exists. * * @see lithium\security\auth\adapter\Form::check * @param array $data The array of raw form data. * @return array Original or sub array of the form data. */ protected function _data($data) { $model = $this->_model; $index = strtolower(Inflector::singularize($model::meta('name'))); return isset($data[$index]) && is_array($data[$index]) ? $data[$index] : $data; } } ?>