PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/src/frapi/library/Frapi/Authorization/HTTP/Digest.php

http://github.com/frapi/frapi
PHP | 258 lines | 108 code | 30 blank | 120 comment | 16 complexity | 318c566cfcb3910a243ce4f2adec565d MD5 | raw file
Possible License(s): BSD-2-Clause
  1. <?php
  2. /**
  3. * Authorization Digest Challenge + Logic
  4. *
  5. *
  6. * LICENSE
  7. *
  8. * This source file is subject to the new BSD license that is bundled
  9. * with this package in the file LICENSE.txt.
  10. * It is also available through the world-wide-web at this URL:
  11. * http://getfrapi.com/license/new-bsd
  12. * If you did not receive a copy of the license and are unable to
  13. * obtain it through the world-wide-web, please send an email
  14. * to license@getfrapi.com so we can send you a copy immediately.
  15. *
  16. * The authorization for partners.
  17. *
  18. * When doing anything about the partners, it
  19. * is going to be taking place here.
  20. *
  21. * @uses Frapi_Authorization
  22. * @uses Frapi_Authorization_Interface
  23. * @license New BSD
  24. * @link http://www.peej.co.uk/projects/phphttpdigest.html
  25. * @link http://en.wikipedia.org/wiki/Digest_access_authentication
  26. * @package frapi
  27. */
  28. class Frapi_Authorization_HTTP_Digest extends Frapi_Authorization implements Frapi_Authorization_Interface
  29. {
  30. /**
  31. * The secret key
  32. *
  33. * @var string The secret key
  34. */
  35. public $secretKey = 'secretKey--&@72';
  36. /**
  37. * The digest opaque value
  38. *
  39. * @var string The opaque value
  40. */
  41. public $opaque = 'opaque';
  42. /**
  43. * The name of the authentication realm
  44. *
  45. * @var string The authentication realm.
  46. */
  47. public $realm = 'FRAPI';
  48. /**
  49. * The base url of the application to authenticate against.
  50. *
  51. * @var string The base url for the authentication of the application.
  52. */
  53. public $baseUrl = '/';
  54. /**
  55. * The life length of the nonce value.
  56. *
  57. * @var integer The nonce life length.
  58. */
  59. public $nonceLife = 300;
  60. /**
  61. * This variable is used to define whether or not the passwords
  62. * should be A1 hashed.
  63. *
  64. * @var boolean True or False.
  65. */
  66. public $passwordsHashed = true;
  67. /**
  68. * This variable contains the parsed digest data
  69. */
  70. protected $digest = null;
  71. /**
  72. * Constructor
  73. *
  74. * The constructor that sets the $this->realm
  75. *
  76. * @param string $realm Perhaps a custom realm. Default is null so the
  77. * realm will be $_SERVER['SERVER_NAME']
  78. */
  79. public function __construct($realm = null)
  80. {
  81. $this->realm = $realm !== null ? $realm : $_SERVER['SERVER_NAME'];
  82. }
  83. /**
  84. * Use a custom secret key.
  85. *
  86. * This method is used to modify the secretKey salt value of
  87. * the Auth object.
  88. *
  89. * @param string $secretKey The new secret key to use when salting
  90. * the authentication challenge.
  91. * @return void
  92. */
  93. public function setSecretKey($secretKey)
  94. {
  95. $this->secretKey = $secretKey;
  96. }
  97. /**
  98. * Send the Authentication digest
  99. *
  100. * This method is used to send the authentication
  101. * negotiation and request the authentication headers
  102. * from the clients.
  103. *
  104. * @return void
  105. */
  106. public function send()
  107. {
  108. header(
  109. 'WWW-Authenticate: Digest ' .
  110. 'realm="' . $this->realm . '", ' .
  111. 'domain="' . $this->baseUrl . '", ' .
  112. 'qop=auth, '.
  113. 'algorithm=MD5, ' .
  114. 'nonce="' . $this->getNonce() . '", ' .
  115. 'opaque="' . $this->getOpaque() . '"'
  116. );
  117. header('HTTP/1.1 401 Unauthorized');
  118. echo 'HTTP Digest Authentication required for "' . $this->realm . '"';
  119. exit(0);
  120. }
  121. /**
  122. * Get the nonce
  123. *
  124. * This method returns the hashed value of a mix of the nonce
  125. * with the lifetime, the user remote addr and the secret key.
  126. *
  127. * @return string A hashed md5 value of the noncelife+remoteaddr+secretKey
  128. */
  129. public function getNonce()
  130. {
  131. $time = ceil(time() / $this->nonceLife) * $this->nonceLife;
  132. $remoteAddress = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
  133. $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
  134. return hash(
  135. 'md5',
  136. date('Y-m-d H:i', $time) . ':' .
  137. $remoteAddress . ':' .
  138. $this->secretKey
  139. );
  140. }
  141. /**
  142. * Get the opaque
  143. *
  144. * This method returns the opaque value hashed in
  145. * an md5.
  146. *
  147. * @return string $this->opaque hashed in md5.
  148. */
  149. public function getOpaque()
  150. {
  151. return hash('md5', $this->opaque);
  152. }
  153. /**
  154. * Authorize the request
  155. *
  156. * This method is used to authorize the request. It fetches the
  157. * digest information from the request, decomposes it and finds out
  158. * the relevant information for authenticating the users.
  159. *
  160. * This method also makes use of Frapi_Model_Partner::isPartnerHandle()
  161. * to validate whether or not a user is a real user. If not then we bail
  162. * early.
  163. *
  164. * @link http://www.peej.co.uk/projects/phphttpdigest.html
  165. *
  166. * @return mixed Either the username of the user making the request or we
  167. * return access to $this->send() which will pop up the authentication
  168. * challenge once again.
  169. */
  170. public function authorize()
  171. {
  172. if (!isset($_SERVER['PHP_AUTH_DIGEST'])) {
  173. return $this->send();
  174. }
  175. if ($this->_parseDigest($_SERVER['PHP_AUTH_DIGEST'])) {
  176. $users = Frapi_Model_Partner::isPartnerHandle($this->digest['username']);
  177. if ($users === false) {
  178. return $this->send();
  179. }
  180. return $this->_validateResponse($users['api_key']);
  181. }
  182. return $this->send();
  183. }
  184. protected function _parseDigest($authorization)
  185. {
  186. if (preg_match('/username="([^"]+)"/', $authorization, $username) &&
  187. preg_match('/[,| ]nonce="([^"]+)"/', $authorization, $nonce) &&
  188. preg_match('/response="([^"]+)"/', $authorization, $response) &&
  189. preg_match('/opaque="([^"]+)"/', $authorization, $opaque) &&
  190. preg_match('/uri="([^"]+)"/', $authorization, $uri))
  191. {
  192. $this->digest = compact('username', 'nonce', 'response', 'opaque', 'uri');
  193. $this->digest['username'] = $this->digest['username'][1];
  194. return true;
  195. }
  196. return false;
  197. }
  198. protected function _validateResponse($data)
  199. {
  200. $requestURI = $_SERVER['REQUEST_URI'];
  201. $_SERVER['X_FRAPI_AUTH_USER'] = $this->digest['username'];
  202. if (strpos($requestURI, '?') !== false) {
  203. $requestURI = substr($requestURI, 0, strlen($this->digest['uri'][1]));
  204. }
  205. if ($this->getOpaque() == $this->digest['opaque'][1] && $requestURI == $this->digest['uri'][1] &&
  206. $this->getNonce() == $this->digest['nonce'][1]) {
  207. $passphrase = hash('md5', "{$this->digest['username']}:{$this->realm}:{$data}");
  208. if ($this->passwordsHashed) {
  209. $a1 = $passphrase;
  210. } else {
  211. $a1 = md5($this->digest['username'] . ':' . $this->realm . ':' . $passphrase);
  212. }
  213. $a2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $requestURI);
  214. if (preg_match('/qop="?([^,\s"]+)/', $_SERVER['PHP_AUTH_DIGEST'], $qop) &&
  215. preg_match('/nc=([^,\s"]+)/', $_SERVER['PHP_AUTH_DIGEST'], $nc) &&
  216. preg_match('/cnonce="([^"]+)"/', $_SERVER['PHP_AUTH_DIGEST'], $cnonce)) {
  217. $expectedResponse =
  218. md5($a1 . ':' . $this->digest['nonce'][1] . ':' . $nc[1] . ':' . $cnonce[1] . ':' . $qop[1] . ':' . $a2);
  219. } else {
  220. $expectedResponse = md5($a1 . ':' . $this->digest['nonce'][1] . ':' . $a2);
  221. }
  222. if ($this->digest['response'][1] == $expectedResponse) {
  223. return $this->digest['username'];
  224. }
  225. }
  226. return $this->send();
  227. }
  228. }