ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
HTTP.php
Go to the documentation of this file.
1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2004 The PHP Group |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license, |
9 // | that is bundled with this package in the file LICENSE, and is |
10 // | available at through the world-wide-web at |
11 // | http://www.php.net/license/2_02.txt. |
12 // | If you did not receive a copy of the PHP license and are unable to |
13 // | obtain it through the world-wide-web, please send a note to |
14 // | license@php.net so we can mail you a copy immediately. |
15 // +----------------------------------------------------------------------+
16 // | Authors: Martin Jansen <mj@php.net> |
17 // | Rui Hirokawa <hirokawa@php.net> |
18 // | David Costa <gurugeek@php.net> |
19 // +----------------------------------------------------------------------+
20 //
21 // $Id: Auth_HTTP.php,v 1.27 2005/04/04 12:48:33 hirokawa Exp $
22 //
23 
24 require_once "Auth/Auth.php";
25 
26 define('AUTH_HTTP_NONCE_TIME_LEN', 16);
27 define('AUTH_HTTP_NONCE_HASH_LEN', 32);
28 
29 // {{{ class Auth_HTTP
30 
54 class Auth_HTTP extends Auth
55 {
56 
57  // {{{ properties
58 
65  var $authType = 'basic';
66 
74  var $realm = "protected area";
75 
83  var $CancelText = "Error 401 - Access denied";
84 
91  var $options = array();
92 
99  var $stale = false;
100 
107  var $opaque = 'dummy';
108 
115  var $uri = '';
116 
123  var $auth = array();
124 
131  var $nextNonce = '';
132 
139  var $nonce = '';
140 
145  var $server;
146 
151  var $post;
152 
157  var $cookie;
158 
159 
160  // }}}
161  // {{{ Constructor
162 
173  function Auth_HTTP($storageDriver, $options = '')
174  {
175  /* set default values for options */
176  $this->options = array('cryptType' => 'md5',
177  'algorithm' => 'MD5',
178  'qop' => 'auth-int,auth',
179  'opaquekey' => 'moo',
180  'noncekey' => 'moo',
181  'digestRealm' => 'protected area',
182  'forceDigestOnly' => false,
183  'nonceLife' => 300,
184  'sessionSharing' => true,
185  );
186 
187  if (!empty($options['authType'])) {
188  $this->authType = strtolower($options['authType']);
189  }
190 
191  if (is_array($options)) {
192  foreach($options as $key => $value) {
193  if (array_key_exists( $key, $this->options)) {
194  $this->options[$key] = $value;
195  }
196  }
197 
198  if (!empty($this->options['opaquekey'])) {
199  $this->opaque = md5($this->options['opaquekey']);
200  }
201  }
202 
203  $this->Auth($storageDriver, $options);
204  }
205 
206  // }}}
207  // {{{ assignData()
208 
217  function assignData()
218  {
219  if (method_exists($this, '_importGlobalVariable')) {
220  $this->server = &$this->_importGlobalVariable('server');
221  }
222 
223 
224  if ($this->authType == 'basic') {
225  if (!empty($this->server['PHP_AUTH_USER'])) {
226  $this->username = $this->server['PHP_AUTH_USER'];
227  }
228 
229  if (!empty($this->server['PHP_AUTH_PW'])) {
230  $this->password = $this->server['PHP_AUTH_PW'];
231  }
232 
236  if (empty($this->username) && empty($this->password)) {
237  if (!empty($this->server['HTTP_AUTHORIZATION'])) {
238  list($this->username, $this->password) =
239  explode(':', base64_decode(substr($this->server['HTTP_AUTHORIZATION'], 6)));
240  }
241  }
242  } elseif ($this->authType == 'digest') {
243  $this->username = '';
244  $this->password = '';
245 
246  $this->digest_header = null;
247  if (!empty($this->server['PHP_AUTH_DIGEST'])) {
248  $this->digest_header = substr($this->server['PHP_AUTH_DIGEST'],
249  strpos($this->server['PHP_AUTH_DIGEST'],' ')+1);
250  } else {
251  $headers = getallheaders();
252  if(isset($headers['Authorization']) && !empty($headers['Authorization'])) {
253  $this->digest_header = substr($headers['Authorization'],
254  strpos($headers['Authorization'],' ')+1);
255  }
256  }
257 
258  if($this->digest_header) {
259  $authtemp = explode(',', $this->digest_header);
260  $auth = array();
261  foreach($authtemp as $key => $value) {
262  $value = trim($value);
263  if(strpos($value,'=') !== false) {
264  $lhs = substr($value,0,strpos($value,'='));
265  $rhs = substr($value,strpos($value,'=')+1);
266  if(substr($rhs,0,1) == '"' && substr($rhs,-1,1) == '"') {
267  $rhs = substr($rhs,1,-1);
268  }
269  $auth[$lhs] = $rhs;
270  }
271  }
272  }
273  if (!isset($auth['uri']) || !isset($auth['realm'])) {
274  return;
275  }
276 
277  if ($this->selfURI() == $auth['uri']) {
278  $this->uri = $auth['uri'];
279  if (substr($headers['Authorization'],0,7) == 'Digest ') {
280 
281  $this->authType = 'digest';
282 
283  if (!isset($auth['nonce']) || !isset($auth['username']) ||
284  !isset($auth['response']) || !isset($auth['qop']) ||
285  !isset($auth['nc']) || !isset($auth['cnonce'])){
286  return;
287  }
288 
289  if ($auth['qop'] != 'auth' && $auth['qop'] != 'auth-int') {
290  return;
291  }
292 
293  $this->stale = $this->_judgeStale($auth['nonce']);
294 
295  if ($this->nextNonce == false) {
296  return;
297  }
298 
299  $this->username = $auth['username'];
300  $this->password = $auth['response'];
301  $this->auth['nonce'] = $auth['nonce'];
302 
303  $this->auth['qop'] = $auth['qop'];
304  $this->auth['nc'] = $auth['nc'];
305  $this->auth['cnonce'] = $auth['cnonce'];
306 
307  if (isset($auth['opaque'])) {
308  $this->auth['opaque'] = $auth['opaque'];
309  }
310 
311  } elseif (substr($headers['Authorization'],0,6) == 'Basic ') {
312  if ($this->options['forceDigestOnly']) {
313  return; // Basic authentication is not allowed.
314  }
315 
316  $this->authType = 'basic';
317  list($username, $password) =
318  explode(':',base64_decode(substr($headers['Authorization'],6)));
319  $this->username = $username;
320  $this->password = $password;
321  }
322  }
323  } else {
324  return PEAR::raiseError('authType is invalid.');
325  }
326 
327  if ($this->options['sessionSharing'] &&
328  isset($this->username) && isset($this->password)) {
329  session_id(md5('Auth_HTTP' . $this->username . $this->password));
330  }
331 
336  $this->_sessionName = "_authhttp".md5($this->realm);
337  }
338 
339  // }}}
340  // {{{ login()
341 
348  function login()
349  {
350  $login_ok = false;
351  if (method_exists($this, '_loadStorage')) {
352  $this->_loadStorage();
353  }
354  $this->storage->_auth_obj->_sessionName =& $this->_sessionName;
355 
360  if (!empty($this->username) && !empty($this->password)) {
361  if ($this->authType == 'basic' && !$this->options['forceDigestOnly']) {
362  if (true === $this->storage->fetchData($this->username, $this->password)) {
363  $login_ok = true;
364  }
365  } else { /* digest authentication */
366 
367  if (!$this->getAuth() || $this->getAuthData('a1') == null) {
368  /*
369  * note:
370  * - only PEAR::DB is supported as container.
371  * - password should be stored in container as plain-text
372  * (if $options['cryptType'] == 'none') or
373  * A1 hashed form (md5('username:realm:password'))
374  * (if $options['cryptType'] == 'md5')
375  */
376  $dbs = $this->storage;
377  if (!DB::isConnection($dbs->db)) {
378  $dbs->_connect($dbs->options['dsn']);
379  }
380 
381  $query = 'SELECT '.$dbs->options['passwordcol']." FROM ".$dbs->options['table'].
382  ' WHERE '.$dbs->options['usernamecol']." = '".
383  $dbs->db->quoteString($this->username)."' ";
384 
385  $pwd = $dbs->db->getOne($query); // password stored in container.
386 
387  if (DB::isError($pwd)) {
388  return PEAR::raiseError($pwd->getMessage(), $pwd->getCode());
389  }
390 
391  if ($this->options['cryptType'] == 'none') {
392  $a1 = md5($this->username.':'.$this->options['digestRealm'].':'.$pwd);
393  } else {
394  $a1 = $pwd;
395  }
396 
397  $this->setAuthData('a1', $a1, true);
398  } else {
399  $a1 = $this->getAuthData('a1');
400  }
401 
402  $login_ok = $this->validateDigest($this->password, $a1);
403  if ($this->nextNonce == false) {
404  $login_ok = false;
405  }
406  }
407 
408  if (!$login_ok && is_callable($this->loginFailedCallback)) {
409  call_user_func($this->loginFailedCallback,$this->username, $this);
410  }
411  }
412 
413  if (!empty($this->username) && $login_ok) {
414  $this->setAuth($this->username);
415  if (is_callable($this->loginCallback)) {
416  call_user_func($this->loginCallback,$this->username, $this);
417  }
418  }
419 
424  if (!empty($this->username) && !$login_ok) {
425  $this->status = AUTH_WRONG_LOGIN;
426  }
427 
428  if ((empty($this->username) || !$login_ok) && $this->showLogin) {
429  $this->drawLogin($this->storage->activeUser);
430  return;
431  }
432 
433  if (!empty($this->username) && $login_ok && $this->authType == 'digest'
434  && $this->auth['qop'] == 'auth') {
435  $this->authenticationInfo();
436  }
437  }
438 
439  // }}}
440  // {{{ drawLogin()
441 
449  function drawLogin($username = "")
450  {
454  if ($this->authType == 'basic') {
455  header("WWW-Authenticate: Basic realm=\"".$this->realm."\"");
456  header('HTTP/1.0 401 Unauthorized');
457  } else if ($this->authType == 'digest') {
458  $this->nonce = $this->_getNonce();
459 
460  $wwwauth = 'WWW-Authenticate: Digest ';
461  $wwwauth .= 'qop="'.$this->options['qop'].'", ';
462  $wwwauth .= 'algorithm='.$this->options['algorithm'].', ';
463  $wwwauth .= 'realm="'.$this->options['digestRealm'].'", ';
464  $wwwauth .= 'nonce="'.$this->nonce.'", ';
465  if ($this->stale) {
466  $wwwauth .= 'stale=true, ';
467  }
468  if (!empty($this->opaque)) {
469  $wwwauth .= 'opaque="'.$this->opaque.'"' ;
470  }
471  $wwwauth .= "\r\n";
472  if (!$this->options['forceDigestOnly']) {
473  $wwwauth .= 'WWW-Authenticate: Basic realm="'.$this->realm.'"';
474  }
475  header($wwwauth);
476  header('HTTP/1.0 401 Unauthorized');
477  }
478 
483  if ($this->stale) {
484  echo 'Stale nonce value, please re-authenticate.';
485  } else {
486  echo $this->CancelText;
487  }
488  exit;
489  }
490 
491  // }}}
492  // {{{ setRealm()
493 
502  function setRealm($realm, $digestRealm = '')
503  {
504  $this->realm = $realm;
505  if (!empty($digestRealm)) {
506  $this->options['digestRealm'] = $digestRealm;
507  }
508  }
509 
510  // }}}
511  // {{{ setCancelText()
512 
520  function setCancelText($text)
521  {
522  $this->CancelText = $text;
523  }
524 
525  // }}}
526  // {{{ validateDigest()
527 
536  function validateDigest($response, $a1)
537  {
538  if (method_exists($this, '_importGlobalVariable')) {
539  $this->server = &$this->_importGlobalVariable('server');
540  }
541 
542  $a2unhashed = $this->server['REQUEST_METHOD'].":".$this->selfURI();
543  if($this->auth['qop'] == 'auth-int') {
544  if(isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
545  // In PHP < 4.3 get raw POST data from this variable
546  $body = $GLOBALS["HTTP_RAW_POST_DATA"];
547  } else if($lines = @file('php://input')) {
548  // In PHP >= 4.3 get raw POST data from this file
549  $body = implode("\n", $lines);
550  } else {
551  if (method_exists($this, '_importGlobalVariable')) {
552  $this->post = &$this->_importGlobalVariable('post');
553  }
554  $body = '';
555  foreach($this->post as $key => $value) {
556  if($body != '') $body .= '&';
557  $body .= rawurlencode($key) . '=' . rawurlencode($value);
558  }
559  }
560 
561  $a2unhashed .= ':'.md5($body);
562  }
563 
564  $a2 = md5($a2unhashed);
565  $combined = $a1.':'.
566  $this->auth['nonce'].':'.
567  $this->auth['nc'].':'.
568  $this->auth['cnonce'].':'.
569  $this->auth['qop'].':'.
570  $a2;
571  $expectedResponse = md5($combined);
572 
573  if(!isset($this->auth['opaque']) || $this->auth['opaque'] == $this->opaque) {
574  if($response == $expectedResponse) { // password is valid
575  if(!$this->stale) {
576  return true;
577  } else {
578  $this->drawLogin();
579  }
580  }
581  }
582 
583  return false;
584  }
585 
586  // }}}
587  // {{{ _judgeStale()
588 
596  function _judgeStale($nonce)
597  {
598  $stale = false;
599 
600  if(!$this->_decodeNonce($nonce, $time, $hash_cli)) {
601  $this->nextNonce = false;
602  $stale = true;
603  return $stale;
604  }
605 
606  if ($time < time() - $this->options['nonceLife']) {
607  $this->nextNonce = $this->_getNonce();
608  $stale = true;
609  } else {
610  $this->nextNonce = $nonce;
611  }
612 
613  return $stale;
614  }
615 
616  // }}}
617  // {{{ _nonceDecode()
618 
628  function _decodeNonce($nonce, &$time, &$hash)
629  {
630  if (method_exists($this, '_importGlobalVariable')) {
631  $this->server = &$this->_importGlobalVariable('server');
632  }
633 
635  return false;
636  }
637 
638  $time = base64_decode(substr($nonce, 0, AUTH_HTTP_NONCE_TIME_LEN));
640 
641  $hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
642 
643  if ($hash_cli != $hash) {
644  return false;
645  }
646 
647  return true;
648  }
649 
650  // }}}
651  // {{{ _getNonce()
652 
659  function _getNonce()
660  {
661  if (method_exists($this, '_importGlobalVariable')) {
662  $this->server = &$this->_importGlobalVariable('server');
663  }
664 
665  $time = time();
666  $hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
667 
668  return base64_encode($time) . $hash;
669  }
670 
671  // }}}
672  // {{{ authenticationInfo()
673 
682  function authenticationInfo($contentMD5 = '') {
683 
684  if($this->getAuth() && ($this->getAuthData('a1') != null)) {
685  $a1 = $this->getAuthData('a1');
686 
687  // Work out authorisation response
688  $a2unhashed = ":".$this->selfURI();
689  if($this->auth['qop'] == 'auth-int') {
690  $a2unhashed .= ':'.$contentMD5;
691  }
692  $a2 = md5($a2unhashed);
693  $combined = $a1.':'.
694  $this->nonce.':'.
695  $this->auth['nc'].':'.
696  $this->auth['cnonce'].':'.
697  $this->auth['qop'].':'.
698  $a2;
699 
700  // Send authentication info
701  $wwwauth = 'Authentication-Info: ';
702  if($this->nonce != $this->nextNonce) {
703  $wwwauth .= 'nextnonce="'.$this->nextNonce.'", ';
704  }
705  $wwwauth .= 'qop='.$this->auth['qop'].', ';
706  $wwwauth .= 'rspauth="'.md5($combined).'", ';
707  $wwwauth .= 'cnonce="'.$this->auth['cnonce'].'", ';
708  $wwwauth .= 'nc='.$this->auth['nc'].'';
709  header($wwwauth);
710  }
711  }
712  // }}}
713  // {{{ setOption()
722  function setOption($name, $value = null)
723  {
724  if (is_array($name)) {
725  foreach($name as $key => $value) {
726  if (array_key_exists( $key, $this->options)) {
727  $this->options[$key] = $value;
728  }
729  }
730  } else {
731  if (array_key_exists( $name, $this->options)) {
732  $this->options[$name] = $value;
733  }
734  }
735  }
736 
737  // }}}
738  // {{{ getOption()
746  function getOption($name)
747  {
748  if (array_key_exists( $name, $this->options)) {
749  return $this->options[$name];
750  }
751  if ($name == 'CancelText') {
752  return $this->CancelText;
753  }
754  if ($name == 'Realm') {
755  return $this->realm;
756  }
757  return false;
758  }
759 
760  // }}}
761  // {{{ selfURI()
768  function selfURI()
769  {
770  if (method_exists($this, '_importGlobalVariable')) {
771  $this->server = &$this->_importGlobalVariable('server');
772  }
773 
774  if (preg_match("/MSIE/",$this->server['HTTP_USER_AGENT'])) {
775  // query string should be removed for MSIE
776  $uri = preg_replace("/^(.*)\?/","\\1",$this->server['REQUEST_URI']);
777  } else {
778  $uri = $this->server['REQUEST_URI'];
779  }
780  return $uri;
781  }
782 
783  // }}}
784 
785 }
786 
787 // }}}
788 
789 /*
790  * Local variables:
791  * tab-width: 4
792  * c-basic-offset: 4
793  * End:
794  */
795 ?>