PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/components/admin/extplorer/libraries/Auth/HTTP/HTTP.php

https://code.google.com/p/mwenhanced/
PHP | 795 lines | 394 code | 104 blank | 297 comment | 139 complexity | 8e8181da494d9f065e35bcd61cd06a76 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, AGPL-1.0, GPL-2.0, MPL-2.0-no-copyleft-exception
  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. require_once "Auth/Auth.php";
  24. define('AUTH_HTTP_NONCE_TIME_LEN', 16);
  25. define('AUTH_HTTP_NONCE_HASH_LEN', 32);
  26. // {{{ class Auth_HTTP
  27. /**
  28. * PEAR::Auth_HTTP
  29. *
  30. * The PEAR::Auth_HTTP class provides methods for creating an
  31. * HTTP authentication system based on RFC-2617 using PHP.
  32. *
  33. * Instead of generating an HTML driven form like PEAR::Auth
  34. * does, this class sends header commands to the clients which
  35. * cause them to present a login box like they are e.g. used
  36. * in Apache's .htaccess mechanism.
  37. *
  38. * This class requires the PEAR::Auth package.
  39. *
  40. * @notes The HTTP Digest Authentication part is based on
  41. * authentication class written by Tom Pike <tom.pike@xiven.com>
  42. *
  43. * @author Martin Jansen <mj@php.net>
  44. * @author Rui Hirokawa <hirokawa@php.net>
  45. * @author David Costa <gurugeek@php.net>
  46. * @package Auth_HTTP
  47. * @extends Auth
  48. * @version $Revision: 1.27 $
  49. */
  50. class Auth_HTTP extends Auth
  51. {
  52. // {{{ properties
  53. /**
  54. * Authorization method: 'basic' or 'digest'
  55. *
  56. * @access public
  57. * @var string
  58. */
  59. var $authType = 'basic';
  60. /**
  61. * Name of the realm for Basic Authentication
  62. *
  63. * @access public
  64. * @var string
  65. * @see drawLogin()
  66. */
  67. var $realm = "protected area";
  68. /**
  69. * Text to send if user hits cancel button
  70. *
  71. * @access public
  72. * @var string
  73. * @see drawLogin()
  74. */
  75. var $CancelText = "Error 401 - Access denied";
  76. /**
  77. * option array
  78. *
  79. * @access public
  80. * @var array
  81. */
  82. var $options = array();
  83. /**
  84. * flag to indicate the nonce was stale.
  85. *
  86. * @access public
  87. * @var bool
  88. */
  89. var $stale = false;
  90. /**
  91. * opaque string for digest authentication
  92. *
  93. * @access public
  94. * @var string
  95. */
  96. var $opaque = 'dummy';
  97. /**
  98. * digest URI
  99. *
  100. * @access public
  101. * @var string
  102. */
  103. var $uri = '';
  104. /**
  105. * authorization info returned by the client
  106. *
  107. * @access public
  108. * @var array
  109. */
  110. var $auth = array();
  111. /**
  112. * next nonce value
  113. *
  114. * @access public
  115. * @var string
  116. */
  117. var $nextNonce = '';
  118. /**
  119. * nonce value
  120. *
  121. * @access public
  122. * @var string
  123. */
  124. var $nonce = '';
  125. /**
  126. * Holds a reference to the global server variable
  127. * @var array
  128. */
  129. var $server;
  130. /**
  131. * Holds a reference to the global post variable
  132. * @var array
  133. */
  134. var $post;
  135. /**
  136. * Holds a reference to the global cookie variable
  137. * @var array
  138. */
  139. var $cookie;
  140. // }}}
  141. // {{{ Constructor
  142. /**
  143. * Constructor
  144. *
  145. * @param string Type of the storage driver
  146. * @param mixed Additional options for the storage driver
  147. * (example: if you are using DB as the storage
  148. * driver, you have to pass the dsn string here)
  149. *
  150. * @return void
  151. */
  152. function Auth_HTTP($storageDriver, $options = '')
  153. {
  154. /* set default values for options */
  155. $this->options = array('cryptType' => 'md5',
  156. 'algorithm' => 'MD5',
  157. 'qop' => 'auth-int,auth',
  158. 'opaquekey' => 'moo',
  159. 'noncekey' => 'moo',
  160. 'digestRealm' => 'protected area',
  161. 'forceDigestOnly' => false,
  162. 'nonceLife' => 300,
  163. 'sessionSharing' => true,
  164. );
  165. if (!empty($options['authType'])) {
  166. $this->authType = strtolower($options['authType']);
  167. }
  168. if (is_array($options)) {
  169. foreach($options as $key => $value) {
  170. if (array_key_exists( $key, $this->options)) {
  171. $this->options[$key] = $value;
  172. }
  173. }
  174. if (!empty($this->options['opaquekey'])) {
  175. $this->opaque = md5($this->options['opaquekey']);
  176. }
  177. }
  178. $this->Auth($storageDriver, $options);
  179. }
  180. // }}}
  181. // {{{ assignData()
  182. /**
  183. * Assign values from $PHP_AUTH_USER and $PHP_AUTH_PW or 'Authorization' header
  184. * to internal variables and sets the session id based
  185. * on them
  186. *
  187. * @access public
  188. * @return void
  189. */
  190. function assignData()
  191. {
  192. if (method_exists($this, '_importGlobalVariable')) {
  193. $this->server = &$this->_importGlobalVariable('server');
  194. }
  195. if ($this->authType == 'basic') {
  196. if (!empty($this->server['PHP_AUTH_USER'])) {
  197. $this->username = $this->server['PHP_AUTH_USER'];
  198. }
  199. if (!empty($this->server['PHP_AUTH_PW'])) {
  200. $this->password = $this->server['PHP_AUTH_PW'];
  201. }
  202. /**
  203. * Try to get authentication information from IIS
  204. */
  205. if (empty($this->username) && empty($this->password)) {
  206. if (!empty($this->server['HTTP_AUTHORIZATION'])) {
  207. list($this->username, $this->password) =
  208. explode(':', base64_decode(substr($this->server['HTTP_AUTHORIZATION'], 6)));
  209. }
  210. }
  211. } elseif ($this->authType == 'digest') {
  212. $this->username = '';
  213. $this->password = '';
  214. $this->digest_header = null;
  215. if (!empty($this->server['PHP_AUTH_DIGEST'])) {
  216. $this->digest_header = substr($this->server['PHP_AUTH_DIGEST'],
  217. strpos($this->server['PHP_AUTH_DIGEST'],' ')+1);
  218. } else {
  219. $headers = getallheaders();
  220. if(isset($headers['Authorization']) && !empty($headers['Authorization'])) {
  221. $this->digest_header = substr($headers['Authorization'],
  222. strpos($headers['Authorization'],' ')+1);
  223. }
  224. }
  225. if($this->digest_header) {
  226. $authtemp = explode(',', $this->digest_header);
  227. $auth = array();
  228. foreach($authtemp as $key => $value) {
  229. $value = trim($value);
  230. if(strpos($value,'=') !== false) {
  231. $lhs = substr($value,0,strpos($value,'='));
  232. $rhs = substr($value,strpos($value,'=')+1);
  233. if(substr($rhs,0,1) == '"' && substr($rhs,-1,1) == '"') {
  234. $rhs = substr($rhs,1,-1);
  235. }
  236. $auth[$lhs] = $rhs;
  237. }
  238. }
  239. }
  240. if (!isset($auth['uri']) || !isset($auth['realm'])) {
  241. return;
  242. }
  243. if ($this->selfURI() == $auth['uri']) {
  244. $this->uri = $auth['uri'];
  245. if (substr($headers['Authorization'],0,7) == 'Digest ') {
  246. $this->authType = 'digest';
  247. if (!isset($auth['nonce']) || !isset($auth['username']) ||
  248. !isset($auth['response']) || !isset($auth['qop']) ||
  249. !isset($auth['nc']) || !isset($auth['cnonce'])){
  250. return;
  251. }
  252. if ($auth['qop'] != 'auth' && $auth['qop'] != 'auth-int') {
  253. return;
  254. }
  255. $this->stale = $this->_judgeStale($auth['nonce']);
  256. if ($this->nextNonce == false) {
  257. return;
  258. }
  259. $this->username = $auth['username'];
  260. $this->password = $auth['response'];
  261. $this->auth['nonce'] = $auth['nonce'];
  262. $this->auth['qop'] = $auth['qop'];
  263. $this->auth['nc'] = $auth['nc'];
  264. $this->auth['cnonce'] = $auth['cnonce'];
  265. if (isset($auth['opaque'])) {
  266. $this->auth['opaque'] = $auth['opaque'];
  267. }
  268. } elseif (substr($headers['Authorization'],0,6) == 'Basic ') {
  269. if ($this->options['forceDigestOnly']) {
  270. return; // Basic authentication is not allowed.
  271. }
  272. $this->authType = 'basic';
  273. list($username, $password) =
  274. explode(':',base64_decode(substr($headers['Authorization'],6)));
  275. $this->username = $username;
  276. $this->password = $password;
  277. }
  278. }
  279. } else {
  280. return PEAR::raiseError('authType is invalid.');
  281. }
  282. if ($this->options['sessionSharing'] &&
  283. isset($this->username) && isset($this->password)) {
  284. session_id(md5('Auth_HTTP' . $this->username . $this->password));
  285. }
  286. /**
  287. * set sessionName for AUTH, so that the sessionName is different
  288. * for distinct realms
  289. */
  290. $this->_sessionName = "_authhttp".md5($this->realm);
  291. }
  292. // }}}
  293. // {{{ login()
  294. /**
  295. * Login function
  296. *
  297. * @access private
  298. * @return void
  299. */
  300. function login()
  301. {
  302. $login_ok = false;
  303. if (method_exists($this, '_loadStorage')) {
  304. $this->_loadStorage();
  305. }
  306. $this->storage->_auth_obj->_sessionName =& $this->_sessionName;
  307. /**
  308. * When the user has already entered a username,
  309. * we have to validate it.
  310. */
  311. if (!empty($this->username) && !empty($this->password)) {
  312. if ($this->authType == 'basic' && !$this->options['forceDigestOnly']) {
  313. if (true === $this->storage->fetchData($this->username, $this->password)) {
  314. $login_ok = true;
  315. }
  316. } else { /* digest authentication */
  317. if (!$this->getAuth() || $this->getAuthData('a1') == null) {
  318. /*
  319. * note:
  320. * - only PEAR::DB is supported as container.
  321. * - password should be stored in container as plain-text
  322. * (if $options['cryptType'] == 'none') or
  323. * A1 hashed form (md5('username:realm:password'))
  324. * (if $options['cryptType'] == 'md5')
  325. */
  326. $dbs = $this->storage;
  327. if (!DB::isConnection($dbs->db)) {
  328. $dbs->_connect($dbs->options['dsn']);
  329. }
  330. $query = 'SELECT '.$dbs->options['passwordcol']." FROM ".$dbs->options['table'].
  331. ' WHERE '.$dbs->options['usernamecol']." = '".
  332. $dbs->db->quoteString($this->username)."' ";
  333. $pwd = $dbs->db->getOne($query); // password stored in container.
  334. if (DB::isError($pwd)) {
  335. return PEAR::raiseError($pwd->getMessage(), $pwd->getCode());
  336. }
  337. if ($this->options['cryptType'] == 'none') {
  338. $a1 = md5($this->username.':'.$this->options['digestRealm'].':'.$pwd);
  339. } else {
  340. $a1 = $pwd;
  341. }
  342. $this->setAuthData('a1', $a1, true);
  343. } else {
  344. $a1 = $this->getAuthData('a1');
  345. }
  346. $login_ok = $this->validateDigest($this->password, $a1);
  347. if ($this->nextNonce == false) {
  348. $login_ok = false;
  349. }
  350. }
  351. if (!$login_ok && is_callable($this->loginFailedCallback)) {
  352. call_user_func($this->loginFailedCallback,$this->username, $this);
  353. }
  354. }
  355. if (!empty($this->username) && $login_ok) {
  356. $this->setAuth($this->username);
  357. if (is_callable($this->loginCallback)) {
  358. call_user_func($this->loginCallback,$this->username, $this);
  359. }
  360. }
  361. /**
  362. * If the login failed or the user entered no username,
  363. * output the login screen again.
  364. */
  365. if (!empty($this->username) && !$login_ok) {
  366. $this->status = AUTH_WRONG_LOGIN;
  367. }
  368. if ((empty($this->username) || !$login_ok) && $this->showLogin) {
  369. $this->drawLogin($this->storage->activeUser);
  370. return;
  371. }
  372. if (!empty($this->username) && $login_ok && $this->authType == 'digest'
  373. && $this->auth['qop'] == 'auth') {
  374. $this->authenticationInfo();
  375. }
  376. }
  377. // }}}
  378. // {{{ drawLogin()
  379. /**
  380. * Launch the login box
  381. *
  382. * @param string $username Username
  383. * @return void
  384. * @access private
  385. */
  386. function drawLogin($username = "")
  387. {
  388. /**
  389. * Send the header commands
  390. */
  391. if ($this->authType == 'basic') {
  392. header("WWW-Authenticate: Basic realm=\"".$this->realm."\"");
  393. header('HTTP/1.0 401 Unauthorized');
  394. } else if ($this->authType == 'digest') {
  395. $this->nonce = $this->_getNonce();
  396. $wwwauth = 'WWW-Authenticate: Digest ';
  397. $wwwauth .= 'qop="'.$this->options['qop'].'", ';
  398. $wwwauth .= 'algorithm='.$this->options['algorithm'].', ';
  399. $wwwauth .= 'realm="'.$this->options['digestRealm'].'", ';
  400. $wwwauth .= 'nonce="'.$this->nonce.'", ';
  401. if ($this->stale) {
  402. $wwwauth .= 'stale=true, ';
  403. }
  404. if (!empty($this->opaque)) {
  405. $wwwauth .= 'opaque="'.$this->opaque.'"' ;
  406. }
  407. $wwwauth .= "\r\n";
  408. if (!$this->options['forceDigestOnly']) {
  409. $wwwauth .= 'WWW-Authenticate: Basic realm="'.$this->realm.'"';
  410. }
  411. header($wwwauth);
  412. header('HTTP/1.0 401 Unauthorized');
  413. }
  414. /**
  415. * This code is only executed if the user hits the cancel
  416. * button or if he enters wrong data 3 times.
  417. */
  418. if ($this->stale) {
  419. echo 'Stale nonce value, please re-authenticate.';
  420. } else {
  421. echo $this->CancelText;
  422. }
  423. exit;
  424. }
  425. // }}}
  426. // {{{ setRealm()
  427. /**
  428. * Set name of the current realm
  429. *
  430. * @access public
  431. * @param string $realm Name of the realm
  432. * @param string $digestRealm Name of the realm for digest authentication
  433. * @return void
  434. */
  435. function setRealm($realm, $digestRealm = '')
  436. {
  437. $this->realm = $realm;
  438. if (!empty($digestRealm)) {
  439. $this->options['digestRealm'] = $digestRealm;
  440. }
  441. }
  442. // }}}
  443. // {{{ setCancelText()
  444. /**
  445. * Set the text to send if user hits the cancel button
  446. *
  447. * @access public
  448. * @param string $text Text to send
  449. * @return void
  450. */
  451. function setCancelText($text)
  452. {
  453. $this->CancelText = $text;
  454. }
  455. // }}}
  456. // {{{ validateDigest()
  457. /**
  458. * judge if the client response is valid.
  459. *
  460. * @access private
  461. * @param string $response client response
  462. * @param string $a1 password or hashed password stored in container
  463. * @return bool true if success, false otherwise
  464. */
  465. function validateDigest($response, $a1)
  466. {
  467. if (method_exists($this, '_importGlobalVariable')) {
  468. $this->server = &$this->_importGlobalVariable('server');
  469. }
  470. $a2unhashed = $this->server['REQUEST_METHOD'].":".$this->selfURI();
  471. if($this->auth['qop'] == 'auth-int') {
  472. if(isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
  473. // In PHP < 4.3 get raw POST data from this variable
  474. $body = $GLOBALS["HTTP_RAW_POST_DATA"];
  475. } else if($lines = @file('php://input')) {
  476. // In PHP >= 4.3 get raw POST data from this file
  477. $body = implode("\n", $lines);
  478. } else {
  479. if (method_exists($this, '_importGlobalVariable')) {
  480. $this->post = &$this->_importGlobalVariable('post');
  481. }
  482. $body = '';
  483. foreach($this->post as $key => $value) {
  484. if($body != '') $body .= '&';
  485. $body .= rawurlencode($key) . '=' . rawurlencode($value);
  486. }
  487. }
  488. $a2unhashed .= ':'.md5($body);
  489. }
  490. $a2 = md5($a2unhashed);
  491. $combined = $a1.':'.
  492. $this->auth['nonce'].':'.
  493. $this->auth['nc'].':'.
  494. $this->auth['cnonce'].':'.
  495. $this->auth['qop'].':'.
  496. $a2;
  497. $expectedResponse = md5($combined);
  498. if(!isset($this->auth['opaque']) || $this->auth['opaque'] == $this->opaque) {
  499. if($response == $expectedResponse) { // password is valid
  500. if(!$this->stale) {
  501. return true;
  502. } else {
  503. $this->drawLogin();
  504. }
  505. }
  506. }
  507. return false;
  508. }
  509. // }}}
  510. // {{{ _judgeStale()
  511. /**
  512. * judge if nonce from client is stale.
  513. *
  514. * @access private
  515. * @param string $nonce nonce value from client
  516. * @return bool stale
  517. */
  518. function _judgeStale($nonce)
  519. {
  520. $stale = false;
  521. if(!$this->_decodeNonce($nonce, $time, $hash_cli)) {
  522. $this->nextNonce = false;
  523. $stale = true;
  524. return $stale;
  525. }
  526. if ($time < time() - $this->options['nonceLife']) {
  527. $this->nextNonce = $this->_getNonce();
  528. $stale = true;
  529. } else {
  530. $this->nextNonce = $nonce;
  531. }
  532. return $stale;
  533. }
  534. // }}}
  535. // {{{ _nonceDecode()
  536. /**
  537. * decode nonce string
  538. *
  539. * @access private
  540. * @param string $nonce nonce value from client
  541. * @param string $time decoded time
  542. * @param string $hash decoded hash
  543. * @return bool false if nonce is invalid
  544. */
  545. function _decodeNonce($nonce, &$time, &$hash)
  546. {
  547. if (method_exists($this, '_importGlobalVariable')) {
  548. $this->server = &$this->_importGlobalVariable('server');
  549. }
  550. if (strlen($nonce) != AUTH_HTTP_NONCE_TIME_LEN + AUTH_HTTP_NONCE_HASH_LEN) {
  551. return false;
  552. }
  553. $time = base64_decode(substr($nonce, 0, AUTH_HTTP_NONCE_TIME_LEN));
  554. $hash_cli = substr($nonce, AUTH_HTTP_NONCE_TIME_LEN, AUTH_HTTP_NONCE_HASH_LEN);
  555. $hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
  556. if ($hash_cli != $hash) {
  557. return false;
  558. }
  559. return true;
  560. }
  561. // }}}
  562. // {{{ _getNonce()
  563. /**
  564. * return nonce to detect timeout
  565. *
  566. * @access private
  567. * @return string nonce value
  568. */
  569. function _getNonce()
  570. {
  571. if (method_exists($this, '_importGlobalVariable')) {
  572. $this->server = &$this->_importGlobalVariable('server');
  573. }
  574. $time = time();
  575. $hash = md5($time . $this->server['HTTP_USER_AGENT'] . $this->options['noncekey']);
  576. return base64_encode($time) . $hash;
  577. }
  578. // }}}
  579. // {{{ authenticationInfo()
  580. /**
  581. * output HTTP Authentication-Info header
  582. *
  583. * @notes md5 hash of contents is required if 'qop' is 'auth-int'
  584. *
  585. * @access private
  586. * @param string MD5 hash of content
  587. */
  588. function authenticationInfo($contentMD5 = '') {
  589. if($this->getAuth() && ($this->getAuthData('a1') != null)) {
  590. $a1 = $this->getAuthData('a1');
  591. // Work out authorisation response
  592. $a2unhashed = ":".$this->selfURI();
  593. if($this->auth['qop'] == 'auth-int') {
  594. $a2unhashed .= ':'.$contentMD5;
  595. }
  596. $a2 = md5($a2unhashed);
  597. $combined = $a1.':'.
  598. $this->nonce.':'.
  599. $this->auth['nc'].':'.
  600. $this->auth['cnonce'].':'.
  601. $this->auth['qop'].':'.
  602. $a2;
  603. // Send authentication info
  604. $wwwauth = 'Authentication-Info: ';
  605. if($this->nonce != $this->nextNonce) {
  606. $wwwauth .= 'nextnonce="'.$this->nextNonce.'", ';
  607. }
  608. $wwwauth .= 'qop='.$this->auth['qop'].', ';
  609. $wwwauth .= 'rspauth="'.md5($combined).'", ';
  610. $wwwauth .= 'cnonce="'.$this->auth['cnonce'].'", ';
  611. $wwwauth .= 'nc='.$this->auth['nc'].'';
  612. header($wwwauth);
  613. }
  614. }
  615. // }}}
  616. // {{{ setOption()
  617. /**
  618. * set authentication option
  619. *
  620. * @access public
  621. * @param mixed $name key of option
  622. * @param mixed $value value of option
  623. * @return void
  624. */
  625. function setOption($name, $value = null)
  626. {
  627. if (is_array($name)) {
  628. foreach($name as $key => $value) {
  629. if (array_key_exists( $key, $this->options)) {
  630. $this->options[$key] = $value;
  631. }
  632. }
  633. } else {
  634. if (array_key_exists( $name, $this->options)) {
  635. $this->options[$name] = $value;
  636. }
  637. }
  638. }
  639. // }}}
  640. // {{{ getOption()
  641. /**
  642. * get authentication option
  643. *
  644. * @access public
  645. * @param string $name key of option
  646. * @return mixed option value
  647. */
  648. function getOption($name)
  649. {
  650. if (array_key_exists( $name, $this->options)) {
  651. return $this->options[$name];
  652. }
  653. if ($name == 'CancelText') {
  654. return $this->CancelText;
  655. }
  656. if ($name == 'Realm') {
  657. return $this->realm;
  658. }
  659. return false;
  660. }
  661. // }}}
  662. // {{{ selfURI()
  663. /**
  664. * get self URI
  665. *
  666. * @access public
  667. * @return string self URI
  668. */
  669. function selfURI()
  670. {
  671. if (method_exists($this, '_importGlobalVariable')) {
  672. $this->server = &$this->_importGlobalVariable('server');
  673. }
  674. if (preg_match("/MSIE/",$this->server['HTTP_USER_AGENT'])) {
  675. // query string should be removed for MSIE
  676. $uri = preg_replace("/^(.*)\?/","\\1",$this->server['REQUEST_URI']);
  677. } else {
  678. $uri = $this->server['REQUEST_URI'];
  679. }
  680. return $uri;
  681. }
  682. // }}}
  683. }
  684. // }}}
  685. /*
  686. * Local variables:
  687. * tab-width: 4
  688. * c-basic-offset: 4
  689. * End:
  690. */
  691. ?>