source: branches/comu/html/test/kakinaka/pear/Auth_HTTP.php @ 2

Revision 2, 23.9 KB checked in by root, 17 years ago (diff)

new import

Line 
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 8719 2006-12-01 05:18:47Z kakinaka $
22//
23
24require_once "Auth.php";
25
26define('AUTH_HTTP_NONCE_TIME_LEN', 16);
27define('AUTH_HTTP_NONCE_HASH_LEN', 32);
28
29// {{{ class Auth_HTTP
30
31/**
32 * PEAR::Auth_HTTP
33 *
34 * The PEAR::Auth_HTTP class provides methods for creating an
35 * HTTP authentication system based on RFC-2617 using PHP.
36 *
37 * Instead of generating an HTML driven form like PEAR::Auth
38 * does, this class sends header commands to the clients which
39 * cause them to present a login box like they are e.g. used
40 * in Apache's .htaccess mechanism.
41 *
42 * This class requires the PEAR::Auth package.
43 *
44 * @notes The HTTP Digest Authentication part is based on
45 *  authentication class written by Tom Pike <tom.pike@xiven.com>
46 *
47 * @author  Martin Jansen <mj@php.net>
48 * @author  Rui Hirokawa <hirokawa@php.net>
49 * @author  David Costa <gurugeek@php.net>
50 * @package Auth_HTTP
51 * @extends Auth
52 * @version $Revision: 8719 $
53 */
54class Auth_HTTP extends Auth
55{
56   
57    // {{{ properties
58
59    /**
60     * Authorization method: 'basic' or 'digest'
61     *
62     * @access public
63     * @var    string
64     */
65    var $authType = 'basic';
66 
67    /**
68     * Name of the realm for Basic Authentication
69     *
70     * @access public
71     * @var    string
72     * @see    drawLogin()
73     */
74    var $realm = "protected area";
75
76    /**
77     * Text to send if user hits cancel button
78     *
79     * @access public
80     * @var    string
81     * @see    drawLogin()
82     */
83    var $CancelText = "Error 401 - Access denied";
84
85    /**
86     * option array
87     *
88     * @access public
89     * @var    array
90     */
91    var $options = array();
92
93    /**
94     * flag to indicate the nonce was stale.
95     *
96     * @access public
97     * @var    bool
98     */
99    var $stale = false;
100
101    /**
102     * opaque string for digest authentication
103     *
104     * @access public
105     * @var    string
106     */
107    var $opaque = 'dummy';
108
109    /**
110     * digest URI
111     *
112     * @access public
113     * @var    string
114     */
115    var $uri = '';
116
117    /**
118     * authorization info returned by the client
119     *
120     * @access public
121     * @var    array
122     */
123    var $auth = array();
124
125    /**
126     * next nonce value
127     *
128     * @access public
129     * @var    string
130     */
131    var $nextNonce = '';
132
133    /**
134     * nonce value
135     *
136     * @access public
137     * @var    string
138     */
139    var $nonce = '';
140
141    /**
142     * Holds a reference to the global server variable
143     * @var array
144     */
145    var $server;
146
147    /**
148     * Holds a reference to the global post variable
149     * @var array
150     */
151    var $post;
152
153    /**
154     * Holds a reference to the global cookie variable
155     * @var array
156     */
157    var $cookie;
158
159
160    // }}}
161    // {{{ Constructor
162
163    /**
164     * Constructor
165     *
166     * @param string    Type of the storage driver
167     * @param mixed     Additional options for the storage driver
168     *                  (example: if you are using DB as the storage
169     *                   driver, you have to pass the dsn string here)
170     *
171     * @return void
172     */
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
209    /**
210     * Assign values from $PHP_AUTH_USER and $PHP_AUTH_PW or 'Authorization' header
211     * to internal variables and sets the session id based
212     * on them
213     *
214     * @access public
215     * @return void
216     */
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           
233            /**
234             * Try to get authentication information from IIS
235             */
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       
332        /**
333         * set sessionName for AUTH, so that the sessionName is different
334         * for distinct realms
335         */
336         $this->_sessionName = "_authhttp".md5($this->realm);
337    }
338
339    // }}}
340    // {{{ login()
341
342    /**
343     * Login function
344     *
345     * @access private
346     * @return void
347     */
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
356        /**
357         * When the user has already entered a username,
358         * we have to validate it.
359         */
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       
420        /**
421         * If the login failed or the user entered no username,
422         * output the login screen again.
423         */
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
442    /**
443     * Launch the login box
444     *
445     * @param  string $username  Username
446     * @return void
447     * @access private
448     */
449    function drawLogin($username = "")
450    {
451        /**
452         * Send the header commands
453         */
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
479        /**
480         * This code is only executed if the user hits the cancel
481         * button or if he enters wrong data 3 times.
482         */
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
494    /**
495     * Set name of the current realm
496     *
497     * @access public
498     * @param  string $realm  Name of the realm
499     * @param  string $digestRealm  Name of the realm for digest authentication
500     * @return void
501     */
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
513    /**
514     * Set the text to send if user hits the cancel button
515     *
516     * @access public
517     * @param  string $text  Text to send
518     * @return void
519     */
520    function setCancelText($text)
521    {
522        $this->CancelText = $text;
523    }
524
525    // }}}
526    // {{{ validateDigest()
527   
528    /**
529     * judge if the client response is valid.
530     *
531     * @access private
532     * @param  string $response  client response
533     * @param  string $a1 password or hashed password stored in container
534     * @return bool true if success, false otherwise
535     */
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   
589    /**
590     * judge if nonce from client is stale.
591     *
592     * @access private
593     * @param  string $nonce  nonce value from client
594     * @return bool stale
595     */
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   
619    /**
620     * decode nonce string
621     *
622     * @access private
623     * @param  string $nonce nonce value from client
624     * @param  string $time decoded time
625     * @param  string $hash decoded hash
626     * @return bool false if nonce is invalid
627     */
628    function _decodeNonce($nonce, &$time, &$hash)
629    {
630        if (method_exists($this, '_importGlobalVariable')) {
631            $this->server = &$this->_importGlobalVariable('server');
632        }
633
634        if (strlen($nonce) != AUTH_HTTP_NONCE_TIME_LEN + AUTH_HTTP_NONCE_HASH_LEN) {
635            return false;
636        }
637
638        $time =  base64_decode(substr($nonce, 0, AUTH_HTTP_NONCE_TIME_LEN));
639        $hash_cli = substr($nonce, AUTH_HTTP_NONCE_TIME_LEN, AUTH_HTTP_NONCE_HASH_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   
653    /**
654     * return nonce to detect timeout
655     *
656     * @access private
657     * @return string nonce value
658     */
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   
674    /**
675     * output HTTP Authentication-Info header
676     *
677     * @notes md5 hash of contents is required if 'qop' is 'auth-int'
678     *
679     * @access private
680     * @param string MD5 hash of content
681     */
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()
714    /**
715     * set authentication option
716     *
717     * @access public
718     * @param mixed $name key of option
719     * @param mixed $value value of option
720     * @return void
721     */
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()
739    /**
740     * get authentication option
741     *
742     * @access public
743     * @param string $name key of option
744     * @return mixed option value
745     */
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()
762    /**
763     * get self URI
764     *
765     * @access public
766     * @return string self URI
767     */
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?>
Note: See TracBrowser for help on using the repository browser.