PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/php/peer/http/DigestAuthorization.class.php

http://github.com/xp-framework/xp-framework
PHP | 247 lines | 134 code | 30 blank | 83 comment | 10 complexity | ba5b8016ce1e7939f693d4380696b60e MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses(
  7. 'peer.Header',
  8. 'security.SecureString',
  9. 'lang.IllegalStateException',
  10. 'lang.MethodNotImplementedException',
  11. 'peer.http.Authorization'
  12. );
  13. /**
  14. * Digest Authorization header
  15. *
  16. * <quote>
  17. * "HTTP/1.0", includes the specification for a Basic Access
  18. * Authentication scheme. This scheme is not considered to be a secure
  19. * method of user authentication (unless used in conjunction with some
  20. * external secure system such as SSL), as the user name and
  21. * password are passed over the network as cleartext.
  22. * </quote>
  23. *
  24. * @see rfc://2617
  25. * @see https://en.wikipedia.org/wiki/Digest_access_authentication
  26. */
  27. class DigestAuthorization extends Authorization {
  28. /* Server values */
  29. private $realm; // Realm
  30. private $qop; // Quality of protection
  31. private $nonce; // Server nonce
  32. private $opaque; // Opaque - optional
  33. /* Internal state */
  34. private $counter= 1; // Client request counter
  35. private $cnonce; // Client nonce
  36. /**
  37. * Constructor
  38. *
  39. * @param string $realm
  40. * @param string $qop
  41. * @param string $nonce
  42. * @param string $opaque
  43. */
  44. public function __construct($realm, $qop, $nonce, $opaque) {
  45. $this->realm= $realm;
  46. $this->qop= $qop;
  47. $this->nonce= $nonce;
  48. $this->opaque= $opaque;
  49. // Initialize client nonce
  50. $this->cnonce();
  51. }
  52. /**
  53. * Read digest realm and accompanying data from HTTP response
  54. * and construct an instance of this class.
  55. *
  56. * @param string $header
  57. * @param string $user
  58. * @param security.SecureString $pass
  59. * @return peer.http.DigestAuthorization
  60. */
  61. public static function fromChallenge($header, $user, $pass) {
  62. if (!preg_match_all('#(([a-z]+)=("[^"$]+)")#m', $header, $matches, PREG_SET_ORDER)) {
  63. throw new IllegalStateException('Invalid WWW-Authenticate line');
  64. }
  65. $values= array(
  66. 'algorithm' => 'md5',
  67. 'opaque' => NULL
  68. );
  69. foreach ($matches as $m) {
  70. $values[$m[2]]= trim($m[3], '"');
  71. }
  72. if ($values['algorithm'] != 'md5') {
  73. throw new MethodNotImplementedException('Digest auth only supported via algo "md5".', 'digest-md5');
  74. }
  75. $auth= new self(
  76. $values['realm'],
  77. $values['qop'],
  78. $values['nonce'],
  79. $values['opaque']
  80. );
  81. $auth->setUsername($user);
  82. $auth->setPassword($pass);
  83. return $auth;
  84. }
  85. /**
  86. * Calculate the response code for the given request
  87. *
  88. * @param peer.http.HttpRequest $request
  89. * @return string
  90. */
  91. public function hashFor($method, $requestUri) {
  92. return md5(implode(':', array(
  93. $this->ha1(),
  94. $this->nonce,
  95. sprintf('%08x', $this->counter),
  96. $this->cnonce,
  97. $this->qop(),
  98. $this->ha2($method, $requestUri)
  99. )));
  100. }
  101. public function getValueRepresentation($method, $requestUri) {
  102. $parts= array(
  103. 'username' => $this->username,
  104. 'realm' => $this->realm,
  105. 'nonce' => $this->nonce,
  106. 'uri' => $requestUri,
  107. 'qop' => $this->qop(),
  108. 'nc' => sprintf('%08x', $this->counter),
  109. 'cnonce' => $this->cnonce,
  110. 'response' => $this->hashFor($method, $requestUri)
  111. );
  112. if (sizeof($this->opaque)) {
  113. $parts['opaque']= $this->opaque;
  114. }
  115. $digest= '';
  116. foreach ($parts as $n => $v) {
  117. $digest.= (!ctype_digit($v)
  118. ? $n.'="'.$v.'", '
  119. : $n.'='.$v.', '
  120. );
  121. }
  122. return 'Digest '.rtrim($digest, ', ');
  123. }
  124. /**
  125. * Sign the given request; ie. add an Authorization: Digest header
  126. * and increase the internal nonce counter.
  127. *
  128. * @param peer.http.HttpRequest $request
  129. */
  130. public function sign(HttpRequest $request) {
  131. $url= $request->target;
  132. $params= array();
  133. if (is_array($request->parameters)) $params= array_merge($params, $request->parameters);
  134. if ($request->getUrl()->hasParams()) $params= array_merge($params, $request->getUrl()->getParams());
  135. if (sizeof($params)) {
  136. $url.= '?';
  137. foreach ($params as $k => $v) {
  138. $url.= $k.'='.$v.'&';
  139. }
  140. $url= substr($url, 0, -1);
  141. }
  142. $request->setHeader('Authorization', new Header(
  143. 'Authorization',
  144. $this->getValueRepresentation($request->method, $url)
  145. ));
  146. // Increase internal counter
  147. $this->counter++;
  148. }
  149. /**
  150. * Create ha1 value
  151. *
  152. * @return string
  153. */
  154. private function ha1() {
  155. return md5(implode(':', array($this->username, $this->realm, $this->password->getCharacters())));
  156. }
  157. /**
  158. * Create ha2 value
  159. *
  160. * @param string $method
  161. * @param string $requestUri
  162. * @return string
  163. */
  164. private function ha2($method, $requestUri) {
  165. return md5(implode(':', array(strtoupper($method), $requestUri)));
  166. }
  167. /**
  168. * Retrieve quality-of-protection value; hardcoded
  169. * @return [type] [description]
  170. */
  171. private function qop() {
  172. $qop= explode(',', $this->qop);
  173. if (!in_array('auth', $qop)) {
  174. throw new MethodNotImplementedException('QoP not given or not supported (supported: "auth", have: '.\xp::stringOf($this->qop).').');
  175. }
  176. return 'auth';
  177. }
  178. /**
  179. * Initialize the client nonce (randomly, if not given a value).
  180. *
  181. * @param string $c default null
  182. */
  183. public function cnonce($c= null) {
  184. if (null === $c) {
  185. $c= substr(md5(uniqid(time())), 0, 8);
  186. }
  187. $this->cnonce= $c;
  188. }
  189. /**
  190. * Check if instance is equal to this instance
  191. *
  192. * @param lang.Generic $o
  193. * @return boolean
  194. */
  195. public function equals($o) {
  196. if (!$o instanceof self) return false;
  197. return (
  198. $o->realm === $this->realm &&
  199. $o->qop === $this->qop &&
  200. $o->nonce === $this->nonce &&
  201. $o->opaque === $this->opaque
  202. );
  203. }
  204. /**
  205. * Retrieve string representation
  206. *
  207. * @return string
  208. */
  209. public function toString() {
  210. $s= $this->getClassName().' ('.$this->hashCode().") {\n";
  211. foreach (array('realm', 'qop', 'nonce', 'opaque', 'username') as $attr) {
  212. $s.= sprintf(" [ %8s ] %s\n", $attr, \xp::stringOf($this->{$attr}));
  213. }
  214. return $s.="}\n";
  215. }
  216. }
  217. ?>