From cab5bf737c52892c8d6706f1ce9c271cef23a1e0 Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Tue, 7 Aug 2007 22:39:39 +1200 Subject: [PATCH] Most of an implementation of Digest authentication is added. --- inc/BasicAuthSession.php | 120 ++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 15 deletions(-) diff --git a/inc/BasicAuthSession.php b/inc/BasicAuthSession.php index 478e6ffb..5ce70e98 100644 --- a/inc/BasicAuthSession.php +++ b/inc/BasicAuthSession.php @@ -1,9 +1,9 @@ * @copyright Catalyst .Net Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 @@ -14,7 +14,7 @@ * * @package rscds */ -class BasicAuthSession { +class HTTPAuthSession { /**#@+ * @access private */ @@ -45,30 +45,119 @@ class BasicAuthSession { /**#@-*/ /** - * The constructor, which pretty much drives it all + * The constructor, which just calls the actual type configured + */ + function HTTPAuthSession() { + global $c; + + if ( $c->http_auth_mode == "Digest" ) { + $this->DigestAuthSession(); + } + else { + $this->BasicAuthSession(); + } + } + + /** + * Authorisation failed, so we send some headers to say so. + * + * @param string $auth_header The WWW-Authenticate header details. + */ + function AuthFailedResponse( $auth_header = "" ) { + global $c; + if ( $auth_header == "" ) { + $auth_header = sprintf( 'WWW-Authenticate: Basic realm="%s"', $c->system_name); + } + + header('HTTP/1.1 401 Unauthorized', true, 401 ); + header('Content-type: text/plain; ; charset="utf-8"' ); + header( $auth_header ); + echo 'Please log in for access to this system.'; + dbg_error_log( "HTTPAuth", ":Session: User is not authorised" ); + exit; + } + + + /** + * Handle Basic HTTP Authentication (not secure unless https) */ function BasicAuthSession() { global $c; if ( isset($_SERVER['PHP_AUTH_USER']) ) { if ( $u = $this->CheckPassword( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { $this->AssignSessionDetails($u); - } - else { - unset($_SERVER['PHP_AUTH_USER']); + return; } } - if (!isset($_SERVER['PHP_AUTH_USER'])) { - header('HTTP/1.1 401 Unauthorized', true, 401 ); - header('Content-type: text/plain; ; charset="utf-8"' ); - header( sprintf( 'WWW-Authenticate: Basic realm="%s"', $c->system_name) ); - echo 'Please log in for access to this system.'; - dbg_error_log( "BasicAuth", ":Session: User is not authorised" ); - exit; - } + $this->AuthFailedResponse(); + // Does not return } + /** + * Handle Digest HTTP Authentication (no passwords were harmed in this transaction!) + * + * Note that this will not actually work, unless we can either: + * (A) store the password plain text in the database + * (B) store an md5( username || realm || password ) in the database + * + * The problem is that potentially means that the administrator can collect the sorts + * of things people use as passwords. I believe this is quite a bad idea. In scenario (B) + * while they cannot see the password itself, they can see a hash which only varies when + * the password varies, so can see when two users have the same password, or can use + * some of the reverse lookup sites to attempt to reverse the hash. I think this is a + * less bad idea, but not ideal. Probably better than running Basic auth of HTTP though! + */ + function DigestAuthSession() { + global $c; + + if ( ! empty($_SERVER['PHP_AUTH_DIGEST'])) { + // analyze the PHP_AUTH_DIGEST variable + if ( $data = $this->ParseDigestHeader($_SERVER['PHP_AUTH_DIGEST']) ) { + // generate the valid response + $user_password = "Don't be silly! Why would a user have a password like this!!?"; + /** + * TODO: At this point we need to query the database for something fitting + * either strategy (A) or (B) above, in order to set $user_password to + * something useful! + */ + $A1 = md5($data['username'] . ':' . $c->system_name . ':' . $user_password); + $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']); + $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2); + + if ( $data['response'] == $valid_response ) { + $this->AssignSessionDetails($u); + return; + } + } + } + + $nonce = uniqid(); + $opaque = md5($c->system_name); + $this->AuthFailedResponse( sprintf('WWW-Authenticate: Digest realm="%s", qop="auth,auth-int", nonce="%s", opaque="%s"', $c->system_name, $nonce, $opaque ) ); + } + + + /** + * Parse the HTTP Digest Auth Header + * - largely sourced from the PHP documentation + */ + function ParseDigestHeader($auth_header) { + // protect against missing data + $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); + $data = array(); + + preg_match_all('@(\w+)=(?:([\'"])([^\2]+)\2|([^\s,]+))@', $auth_header, $matches, PREG_SET_ORDER); + + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed_parts[$m[1]]); + } + + return $needed_parts ? false : $data; + } + /** * CheckPassword does all of the password checking and * returns a user record object, or false if it all ends in tears. @@ -146,4 +235,5 @@ class BasicAuthSession { } + ?> \ No newline at end of file