<?php

/**
 * @package KantPHP
 * @author  Zhenqiang Zhang <zhenqiang.zhang@hotmail.com>
 * @copyright (c) KantPHP Studio, All rights reserved.
 * @license http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
 */
namespace Kant\Filter\Auth;

/**
 * HttpBasicAuth is an action filter that supports the HTTP Basic authentication method.
 *
 * You may use HttpBasicAuth by attaching it as a behavior to a controller or module, like the following:
 *
 * ```php
 * public function behaviors()
 * {
 * return [
 * 'basicAuth' => [
 * 'class' => \Kant\Filter\Auth\HttpBasicAuth::className(),
 * ],
 * ];
 * }
 * ```
 *
 * The default implementation of HttpBasicAuth uses the [[\yii\web\User::loginByAccessToken()|loginByAccessToken()]]
 * method of the `user` application component and only passes the user name. This implementation is used
 * for authenticating API clients.
 *
 * If you want to authenticate users using username and password, you should provide the [[auth]] function for example like the following:
 *
 * ```php
 * public function behaviors()
 * {
 * return [
 * 'basicAuth' => [
 * 'class' => \Kant\Filter\Auth\HttpBasicAuth::className(),
 * 'auth' => function ($username, $password) {
 * $user = User::find()->where(['username' => $username])->one();
 * if ($user->verifyPassword($password)) {
 * return $user;
 * }
 * return null;
 * },
 * ],
 * ];
 * }
 * ```
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class HttpBasicAuth extends AuthMethod
{

    /**
     *
     * @var string the HTTP authentication realm
     */
    public $realm = 'api';

    /**
     *
     * @var callable a PHP callable that will authenticate the user with the HTTP basic auth information.
     *      The callable receives a username and a password as its parameters. It should return an identity object
     *      that matches the username and password. Null should be returned if there is no such identity.
     *     
     *      The following code is a typical implementation of this callable:
     *     
     *      ```php
     *      function ($username, $password) {
     *      return \app\models\User::findOne([
     *      'username' => $username,
     *      'password' => $password,
     *      ]);
     *      }
     *      ```
     *     
     *      If this property is not set, the username information will be considered as an access token
     *      while the password information will be ignored. The [[\yii\web\User::loginByAccessToken()]]
     *      method will be called to authenticate and login the user.
     */
    public $auth;

    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        $username = $request->getAuthUser();
        $password = $request->getAuthPassword();
        
        if ($this->auth) {
            if ($username !== null || $password !== null) {
                $identity = call_user_func($this->auth, $username, $password);
                if ($identity !== null) {
                    $user->switchIdentity($identity);
                } else {
                    $this->handleFailure($response);
                }
                return $identity;
            }
        } elseif ($username !== null) {
            $identity = $user->loginByAccessToken($username, get_class($this));
            if ($identity === null) {
                $this->handleFailure($response);
            }
            return $identity;
        }
        
        return null;
    }

    /**
     * @inheritdoc
     */
    public function challenge($response)
    {
        $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
    }
}
