CakePHPのソースコードを見てたら、FormAuthenticateが面白かった

今回CakePHPのソースコードを読んでみて、面白いところがあったので、切り取って参照してみようと思う。

https://github.com/cakephp/cakephp/blob/3.next-entity-methods/src/Auth/FormAuthenticate.php

FormAuthenticate.phpである。
これから変化するかもしれないので、以下にコピペしようかと思う。

```php
namespace Cake\Auth;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
/**
 * An authentication adapter for AuthComponent. Provides the ability to authenticate using POST
 * data. Can be used by configuring AuthComponent to use it via the AuthComponent::$authenticate config.
 *
 * ```
 *  $this->Auth->authenticate = [
 *      'Form' => [
 *          'finder' => ['auth' => ['some_finder_option' => 'some_value']]
 *      ]
 *  ]
 * ```
 *
 * When configuring FormAuthenticate you can pass in config to which fields, model and additional conditions
 * are used. See FormAuthenticate::$_config for more information.
 *
 * @see \Cake\Controller\Component\AuthComponent::$authenticate
 */
class FormAuthenticate extends BaseAuthenticate
{
    /**
     * Checks the fields to ensure they are supplied.
     *
     * @param \Cake\Http\ServerRequest $request The request that contains login information.
     * @param array $fields The fields to be checked.
     * @return bool False if the fields have not been supplied. True if they exist.
     */
    protected function _checkFields(ServerRequest $request, array $fields)
    {
        foreach ([$fields['username'], $fields['password']] as $field) {
            $value = $request->getData($field);
            if (empty($value) || !is_string($value)) {
                return false;
            }
        }
        return true;
    }
    /**
     * Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields`
     * to find POST data that is used to find a matching record in the `config.userModel`. Will return false if
     * there is no post data, either username or password is missing, or if the scope conditions have not been met.
     *
     * @param \Cake\Http\ServerRequest $request The request that contains login information.
     * @param \Cake\Http\Response $response Unused response object.
     * @return mixed False on login failure. An array of User data on success.
     */
    public function authenticate(ServerRequest $request, Response $response)
    {
        $fields = $this->_config['fields'];
        if (!$this->_checkFields($request, $fields)) {
            return false;
        }
        return $this->_findUser(
            $request->getData($fields['username']),
            $request->getData($fields['password'])
        );
    }
}
```

An authentication adapter for AuthComponent. Provides the ability to authenticate using POST data. Can be used by configuring AuthComponent to use it via the AuthComponent::$authenticate config.

AuthComponentの認証アダプタです。POSTデータを利用して、認証する能力を供給するオブジェクトです。AuthComponent::$authenticateの構成を利用して、AuthComponentを構成します。

以下の関数では、フィールドにデータが供給されているかどうかをチェックします。

```php
    protected function _checkFields(ServerRequest $request, array $fields)
    {
        foreach ([$fields['username'], $fields['password']] as $field) {
            $value = $request->getData($field);
            if (empty($value) || !is_string($value)) {
                return false;
            }
        }
        return true;
    }
```
\$requestに、ServerRequestの型で、サーバへのリクエストが格納されています。\$fieldsは、ハッシュの形式です。\$fieldsから'username'と'password'がキーのものを取り出し、一つ一つ、\$fieldに格納します。
一つ一つ格納した\$fieldによって、

```php
$request->getData($field);
```
によって、リクエストの中に、\$fieldに相当するものが存在するかどうかをチェックします。
そして、それがセットされていれば、trueを返し、そうでなければ、falseを返します。
ここのロジックでは、'username'と'password'が両方セットされていた時のみ、trueを返し、どちらかがセットされていない場合は、falseを返します。
ここでチェックしているのは、'username'と'password'がセットされているということであり、認証しているわけではありません。ちなみに、これは、以下の認証メソッドの内部のみでしか使用されていないです。

```php
    public function authenticate(ServerRequest $request, Response $response)
    {
        $fields = $this->_config['fields'];
        if (!$this->_checkFields($request, $fields)) {
            return false;
        }

        return $this->_findUser(
            $request->getData($fields['username']),
            $request->getData($fields['password'])
        );
    }
```
 Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields` to find POST data that is used to find a matching record in the `config.userModel`. Will return false if　there is no post data, either username or password is missing, or if the scope conditions have not been met.

リクエストに含まれる個人を認証します。`config.userModel`と`config.fields`を使用して、POSTデータが`config.userModel`に含まれているかどうかを調べるのに使われます。usernameかpasswordが失われているなど、POSTデータが存在しない場合や、条件が合わない場合は、falseが返されます。
　ちなみに、条件が合った場合は、_findUser（）関数が返す、配列を返すようです。

では、_findUser()関数で何が呼ばれているのでしょうか？タグジャンプをしてみると、以下のメソッドに呼びます。

```php
    protected function _findUser($username, $password = null)
    {
        $result = $this->_query($username)->first();

        if (empty($result)) {
            return false;
        }

        $passwordField = $this->_config['fields']['password'];
        if ($password !== null) {
            $hasher = $this->passwordHasher();
            $hashedPassword = $result->get($passwordField);
            if (!$hasher->check($password, $hashedPassword)) {
                return false;
            }

            $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
            $result->unsetProperty($passwordField);
        }
        $hidden = $result->getHidden();
        if ($password === null && in_array($passwordField, $hidden)) {
            $key = array_search($passwordField, $hidden);
            unset($hidden[$key]);
            $result->setHidden($hidden);
        }

        return $result->toArray();
    }
```

https://github.com/cakephp/cakephp/blob/3.next-entity-methods/src/Auth/BaseAuthenticate.php#L105-L125

```php
$result = $this->_query($username)->first();
```
によって、指定のusernameにおいての、データベースにおいての最初の結果を取得します。

```php
        if (empty($result)) {
            return false;
        }
```
によって、usernameがデータベースにセットされていなかった場合は、falseを返します。

これ以降がややこしいルーチンになっていますが、以下の説明を読んでからソース読んだ方が良さそうです。

Input passwords will be hashed even when a user doesn't exist. This helps mitigate timing attacks that are attempting to find valid usernames.

ユーザーが存在しない場合でも、パスワードはハッシュされます。これは、有効なユーザー名を発見しようと試行するタイミング攻撃を緩和します。
そう書かれていますが、ユーザーが存在しない場合は、return falseで、falseを返して、パスワードをハッシュしていないように見えます。

```php
        $passwordField = $this->_config['fields']['password'];
        if ($password !== null) {
            $hasher = $this->passwordHasher();
            $hashedPassword = $result->get($passwordField);
            if (!$hasher->check($password, $hashedPassword)) {
                return false;
            }

            $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword);
            $result->unsetProperty($passwordField);
        }
```
_configからパスワードフィールドを取得し、パスワードをハッシャーにかけてチェックしています。パスワードはそのまま保存すると、漏洩した時に大変なことになるから、必ずハッシュをかけるんですね。

```php
        $hidden = $result->getHidden();
        if ($password === null && in_array($passwordField, $hidden)) {
            $key = array_search($passwordField, $hidden);
            unset($hidden[$key]);
            $result->setHidden($hidden);
        }
```
ここで、\$passwordがnullであるなら、hiddenのパスワードフィールドを取り出し、\$hiddenを何かにセットしているようですが、ここはよくわからないですね。
では、続き読んでいきます。