pictcode / lib / Cake / Controller / Component / Auth / DigestAuthenticate.php @ 19966135
履歴 | 表示 | アノテート | ダウンロード (7.591 KB)
| 1 | 
      <?php
     | 
  
|---|---|
| 2 | 
      /**
     | 
  
| 3 | 
       * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
     | 
  
| 4 | 
       * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
     | 
  
| 11 | 
       * @link          http://cakephp.org CakePHP(tm) Project
     | 
  
| 12 | 
       * @license       http://www.opensource.org/licenses/mit-license.php MIT License
     | 
  
| 13 | 
       */
     | 
  
| 14 | 
       | 
  
| 15 | 
      App::uses('BasicAuthenticate', 'Controller/Component/Auth');  | 
  
| 16 | 
       | 
  
| 17 | 
      /**
     | 
  
| 18 | 
       * Digest Authentication adapter for AuthComponent.
     | 
  
| 19 | 
       *
     | 
  
| 20 | 
       * Provides Digest HTTP authentication support for AuthComponent. Unlike most AuthComponent adapters,
     | 
  
| 21 | 
       * DigestAuthenticate requires a special password hash that conforms to RFC2617. You can create this
     | 
  
| 22 | 
       * password using `DigestAuthenticate::password()`. If you wish to use digest authentication alongside other
     | 
  
| 23 | 
       * authentication methods, its recommended that you store the digest authentication separately.
     | 
  
| 24 | 
       *
     | 
  
| 25 | 
       * Clients using Digest Authentication must support cookies. Since AuthComponent identifies users based
     | 
  
| 26 | 
       * on Session contents, clients without support for cookies will not function properly.
     | 
  
| 27 | 
       *
     | 
  
| 28 | 
       * ### Using Digest auth
     | 
  
| 29 | 
       *
     | 
  
| 30 | 
       * In your controller's components array, add auth + the required settings.
     | 
  
| 31 | 
       * ```
     | 
  
| 32 | 
       *        public $components = array(
     | 
  
| 33 | 
       *                'Auth' => array(
     | 
  
| 34 | 
       *                        'authenticate' => array('Digest')
     | 
  
| 35 | 
       *                )
     | 
  
| 36 | 
       *        );
     | 
  
| 37 | 
       * ```
     | 
  
| 38 | 
       *
     | 
  
| 39 | 
       * In your login function just call `$this->Auth->login()` without any checks for POST data. This
     | 
  
| 40 | 
       * will send the authentication headers, and trigger the login dialog in the browser/client.
     | 
  
| 41 | 
       *
     | 
  
| 42 | 
       * ### Generating passwords compatible with Digest authentication.
     | 
  
| 43 | 
       *
     | 
  
| 44 | 
       * Due to the Digest authentication specification, digest auth requires a special password value. You
     | 
  
| 45 | 
       * can generate this password using `DigestAuthenticate::password()`
     | 
  
| 46 | 
       *
     | 
  
| 47 | 
       * `$digestPass = DigestAuthenticate::password($username, env('SERVER_NAME'), $password);`
     | 
  
| 48 | 
       *
     | 
  
| 49 | 
       * Its recommended that you store this digest auth only password separate from password hashes used for other
     | 
  
| 50 | 
       * login methods. For example `User.digest_pass` could be used for a digest password, while `User.password` would
     | 
  
| 51 | 
       * store the password hash for use with other methods like Basic or Form.
     | 
  
| 52 | 
       *
     | 
  
| 53 | 
       * @package       Cake.Controller.Component.Auth
     | 
  
| 54 | 
       * @since 2.0
     | 
  
| 55 | 
       */
     | 
  
| 56 | 
      class DigestAuthenticate extends BasicAuthenticate {  | 
  
| 57 | 
       | 
  
| 58 | 
      /**
     | 
  
| 59 | 
       * Settings for this object.
     | 
  
| 60 | 
       *
     | 
  
| 61 | 
       * - `fields` The fields to use to identify a user by.
     | 
  
| 62 | 
       * - `userModel` The model name of the User, defaults to User.
     | 
  
| 63 | 
       * - `userFields` Array of fields to retrieve from User model, null to retrieve all. Defaults to null.
     | 
  
| 64 | 
       * - `scope` Additional conditions to use when looking up and authenticating users,
     | 
  
| 65 | 
       *    i.e. `array('User.is_active' => 1).`
     | 
  
| 66 | 
       * - `recursive` The value of the recursive key passed to find(). Defaults to 0.
     | 
  
| 67 | 
       * - `contain` Extra models to contain and store in session.
     | 
  
| 68 | 
       * - `realm` The realm authentication is for, Defaults to the servername.
     | 
  
| 69 | 
       * - `nonce` A nonce used for authentication. Defaults to `uniqid()`.
     | 
  
| 70 | 
       * - `qop` Defaults to auth, no other values are supported at this time.
     | 
  
| 71 | 
       * - `opaque` A string that must be returned unchanged by clients.
     | 
  
| 72 | 
       *    Defaults to `md5($settings['realm'])`
     | 
  
| 73 | 
       *
     | 
  
| 74 | 
       * @var array
     | 
  
| 75 | 
       */
     | 
  
| 76 | 
      public $settings = array(  | 
  
| 77 | 
      'fields' => array(  | 
  
| 78 | 
      'username' => 'username',  | 
  
| 79 | 
      'password' => 'password'  | 
  
| 80 | 
      ),  | 
  
| 81 | 
      'userModel' => 'User',  | 
  
| 82 | 
      'userFields' => null,  | 
  
| 83 | 
      'scope' => array(),  | 
  
| 84 | 
      'recursive' => 0,  | 
  
| 85 | 
      'contain' => null,  | 
  
| 86 | 
      'realm' => '',  | 
  
| 87 | 
      'qop' => 'auth',  | 
  
| 88 | 
      'nonce' => '',  | 
  
| 89 | 
      'opaque' => '',  | 
  
| 90 | 
      'passwordHasher' => 'Simple',  | 
  
| 91 | 
      );  | 
  
| 92 | 
       | 
  
| 93 | 
      /**
     | 
  
| 94 | 
       * Constructor, completes configuration for digest authentication.
     | 
  
| 95 | 
       *
     | 
  
| 96 | 
       * @param ComponentCollection $collection The Component collection used on this request.
     | 
  
| 97 | 
       * @param array $settings An array of settings.
     | 
  
| 98 | 
       */
     | 
  
| 99 | 
      public function __construct(ComponentCollection $collection, $settings) {  | 
  
| 100 | 
      parent::__construct($collection, $settings);  | 
  
| 101 | 
      if (empty($this->settings['nonce'])) {  | 
  
| 102 | 
      $this->settings['nonce'] = uniqid('');  | 
  
| 103 | 
      }  | 
  
| 104 | 
      if (empty($this->settings['opaque'])) {  | 
  
| 105 | 
      $this->settings['opaque'] = md5($this->settings['realm']);  | 
  
| 106 | 
      }  | 
  
| 107 | 
      }  | 
  
| 108 | 
       | 
  
| 109 | 
      /**
     | 
  
| 110 | 
       * Get a user based on information in the request. Used by cookie-less auth for stateless clients.
     | 
  
| 111 | 
       *
     | 
  
| 112 | 
       * @param CakeRequest $request Request object.
     | 
  
| 113 | 
       * @return mixed Either false or an array of user information
     | 
  
| 114 | 
       */
     | 
  
| 115 | 
      public function getUser(CakeRequest $request) {  | 
  
| 116 | 
      $digest = $this->_getDigest();  | 
  
| 117 | 
      if (empty($digest)) {  | 
  
| 118 | 
      return false;  | 
  
| 119 | 
      }  | 
  
| 120 | 
       | 
  
| 121 | 
      list(, $model) = pluginSplit($this->settings['userModel']);  | 
  
| 122 | 
      $user = $this->_findUser(array(  | 
  
| 123 | 
      $model . '.' . $this->settings['fields']['username'] => $digest['username']  | 
  
| 124 | 
      ));  | 
  
| 125 | 
      if (empty($user)) {  | 
  
| 126 | 
      return false;  | 
  
| 127 | 
      }  | 
  
| 128 | 
      $password = $user[$this->settings['fields']['password']];  | 
  
| 129 | 
      unset($user[$this->settings['fields']['password']]);  | 
  
| 130 | 
      if ($digest['response'] === $this->generateResponseHash($digest, $password)) {  | 
  
| 131 | 
      return $user;  | 
  
| 132 | 
      }  | 
  
| 133 | 
      return false;  | 
  
| 134 | 
      }  | 
  
| 135 | 
       | 
  
| 136 | 
      /**
     | 
  
| 137 | 
       * Gets the digest headers from the request/environment.
     | 
  
| 138 | 
       *
     | 
  
| 139 | 
       * @return array Array of digest information.
     | 
  
| 140 | 
       */
     | 
  
| 141 | 
      protected function _getDigest() {  | 
  
| 142 | 
      $digest = env('PHP_AUTH_DIGEST');  | 
  
| 143 | 
      if (empty($digest) && function_exists('apache_request_headers')) {  | 
  
| 144 | 
                              $headers = apache_request_headers();
     | 
  
| 145 | 
      if (!empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) === 'Digest ') {  | 
  
| 146 | 
      $digest = substr($headers['Authorization'], 7);  | 
  
| 147 | 
      }  | 
  
| 148 | 
      }  | 
  
| 149 | 
      if (empty($digest)) {  | 
  
| 150 | 
      return false;  | 
  
| 151 | 
      }  | 
  
| 152 | 
      return $this->parseAuthData($digest);  | 
  
| 153 | 
      }  | 
  
| 154 | 
       | 
  
| 155 | 
      /**
     | 
  
| 156 | 
       * Parse the digest authentication headers and split them up.
     | 
  
| 157 | 
       *
     | 
  
| 158 | 
       * @param string $digest The raw digest authentication headers.
     | 
  
| 159 | 
       * @return array|null An array of digest authentication headers
     | 
  
| 160 | 
       */
     | 
  
| 161 | 
      public function parseAuthData($digest) {  | 
  
| 162 | 
      if (substr($digest, 0, 7) === 'Digest ') {  | 
  
| 163 | 
      $digest = substr($digest, 7);  | 
  
| 164 | 
      }  | 
  
| 165 | 
      $keys = $match = array();  | 
  
| 166 | 
      $req = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);  | 
  
| 167 | 
      preg_match_all('/(\w+)=([\'"]?)([a-zA-Z0-9\:\#\%\?\&@=\.\/_-]+)\2/', $digest, $match, PREG_SET_ORDER);  | 
  
| 168 | 
       | 
  
| 169 | 
      foreach ($match as $i) {  | 
  
| 170 | 
      $keys[$i[1]] = $i[3];  | 
  
| 171 | 
      unset($req[$i[1]]);  | 
  
| 172 | 
      }  | 
  
| 173 | 
       | 
  
| 174 | 
      if (empty($req)) {  | 
  
| 175 | 
      return $keys;  | 
  
| 176 | 
      }  | 
  
| 177 | 
      return null;  | 
  
| 178 | 
      }  | 
  
| 179 | 
       | 
  
| 180 | 
      /**
     | 
  
| 181 | 
       * Generate the response hash for a given digest array.
     | 
  
| 182 | 
       *
     | 
  
| 183 | 
       * @param array $digest Digest information containing data from DigestAuthenticate::parseAuthData().
     | 
  
| 184 | 
       * @param string $password The digest hash password generated with DigestAuthenticate::password()
     | 
  
| 185 | 
       * @return string Response hash
     | 
  
| 186 | 
       */
     | 
  
| 187 | 
      public function generateResponseHash($digest, $password) {  | 
  
| 188 | 
      return md5(  | 
  
| 189 | 
                              $password .
     | 
  
| 190 | 
      ':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' .  | 
  
| 191 | 
      md5(env('REQUEST_METHOD') . ':' . $digest['uri'])  | 
  
| 192 | 
      );  | 
  
| 193 | 
      }  | 
  
| 194 | 
       | 
  
| 195 | 
      /**
     | 
  
| 196 | 
       * Creates an auth digest password hash to store
     | 
  
| 197 | 
       *
     | 
  
| 198 | 
       * @param string $username The username to use in the digest hash.
     | 
  
| 199 | 
       * @param string $password The unhashed password to make a digest hash for.
     | 
  
| 200 | 
       * @param string $realm The realm the password is for.
     | 
  
| 201 | 
       * @return string the hashed password that can later be used with Digest authentication.
     | 
  
| 202 | 
       */
     | 
  
| 203 | 
      public static function password($username, $password, $realm) {  | 
  
| 204 | 
      return md5($username . ':' . $realm . ':' . $password);  | 
  
| 205 | 
      }  | 
  
| 206 | 
       | 
  
| 207 | 
      /**
     | 
  
| 208 | 
       * Generate the login headers
     | 
  
| 209 | 
       *
     | 
  
| 210 | 
       * @return string Headers for logging in.
     | 
  
| 211 | 
       */
     | 
  
| 212 | 
      public function loginHeaders() {  | 
  
| 213 | 
      $options = array(  | 
  
| 214 | 
      'realm' => $this->settings['realm'],  | 
  
| 215 | 
      'qop' => $this->settings['qop'],  | 
  
| 216 | 
      'nonce' => $this->settings['nonce'],  | 
  
| 217 | 
      'opaque' => $this->settings['opaque']  | 
  
| 218 | 
      );  | 
  
| 219 | 
      $opts = array();  | 
  
| 220 | 
      foreach ($options as $k => $v) {  | 
  
| 221 | 
      $opts[] = sprintf('%s="%s"', $k, $v);  | 
  
| 222 | 
      }  | 
  
| 223 | 
      return 'WWW-Authenticate: Digest ' . implode(',', $opts);  | 
  
| 224 | 
      }  | 
  
| 225 | 
       | 
  
| 226 | 
      }  |