From 3e512abbe4fc655e065e552797d1322af8dc08df Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Thu, 2 Aug 2012 16:29:00 +0800 Subject: [PATCH] MDL-22663 Repository: WebDAV supports Digest authentication --- lib/webdavlib.php | 103 +++++++++++++++++++++++++++++++++++++- repository/webdav/lib.php | 2 +- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/lib/webdavlib.php b/lib/webdavlib.php index 1280a3d54e6..027de7344e5 100644 --- a/lib/webdavlib.php +++ b/lib/webdavlib.php @@ -73,6 +73,9 @@ class webdav_client { private $_body=''; private $_connection_closed = false; private $_maxheaderlenth = 1000; + private $_digestchallenge = null; + private $_cnonce = ''; + private $_nc = 0; /**#@-*/ @@ -1293,7 +1296,6 @@ EOD; * @access private */ private function create_basic_request($method) { - $request = ''; $this->header_add(sprintf('%s %s %s', $method, $this->_path, $this->_protocol)); $this->header_add(sprintf('Host: %s:%s', $this->_server, $this->_port)); //$request .= sprintf('Connection: Keep-Alive'); @@ -1302,9 +1304,104 @@ EOD; $this->header_add('TE: Trailers'); if ($this->_auth == 'basic') { $this->header_add(sprintf('Authorization: Basic %s', base64_encode("$this->_user:$this->_pass"))); + } else if ($this->_auth == 'digest') { + if ($signature = $this->digest_signature($method)){ + $this->header_add($signature); + } } } + /** + * Reads the header, stores the challenge information + * + * @return void + */ + private function digest_auth() { + + $headers = array(); + $headers[] = sprintf('%s %s %s', 'HEAD', $this->_path, $this->_protocol); + $headers[] = sprintf('Host: %s:%s', $this->_server, $this->_port); + $headers[] = sprintf('User-Agent: %s', $this->_user_agent); + $headers = implode("\r\n", $headers); + $headers .= "\r\n\r\n"; + fputs($this->sock, $headers); + + // Reads the headers. + $i = 0; + $header = ''; + do { + $header .= fread($this->sock, 1); + $i++; + } while (!preg_match('/\\r\\n\\r\\n$/', $header, $matches) && $i < $this->_maxheaderlenth); + + // Analyse the headers. + $digest = array(); + $splitheaders = explode("\r\n", $header); + foreach ($splitheaders as $line) { + if (!preg_match('/^WWW-Authenticate: Digest/', $line)) { + continue; + } + $line = substr($line, strlen('WWW-Authenticate: Digest ')); + $params = explode(',', $line); + foreach ($params as $param) { + list($key, $value) = explode('=', trim($param), 2); + $digest[$key] = trim($value, '"'); + } + break; + } + + $this->_digestchallenge = $digest; + } + + /** + * Generates the digest signature + * + * @return string signature to add to the headers + * @access private + */ + private function digest_signature($method) { + if (!$this->_digestchallenge) { + $this->digest_auth(); + } + + $signature = array(); + $signature['username'] = '"' . $this->_user . '"'; + $signature['realm'] = '"' . $this->_digestchallenge['realm'] . '"'; + $signature['nonce'] = '"' . $this->_digestchallenge['nonce'] . '"'; + $signature['uri'] = '"' . $this->_path . '"'; + + if (isset($this->_digestchallenge['algorithm']) && $this->_digestchallenge['algorithm'] != 'MD5') { + $this->_error_log('Algorithm other than MD5 are not supported'); + return false; + } + + $a1 = $this->_user . ':' . $this->_digestchallenge['realm'] . ':' . $this->_pass; + $a2 = $method . ':' . $this->_path; + + if (!isset($this->_digestchallenge['qop'])) { + $signature['response'] = '"' . md5(md5($a1) . ':' . $this->_digestchallenge['nonce'] . ':' . md5($a2)) . '"'; + } else { + // Assume QOP is auth + if (empty($this->_cnonce)) { + $this->_cnonce = random_string(); + $this->_nc = 0; + } + $this->_nc++; + $nc = sprintf('%08d', $this->_nc); + $signature['cnonce'] = '"' . $this->_cnonce . '"'; + $signature['nc'] = '"' . $nc . '"'; + $signature['qop'] = '"' . $this->_digestchallenge['qop'] . '"'; + $signature['response'] = '"' . md5(md5($a1) . ':' . $this->_digestchallenge['nonce'] . ':' . + $nc . ':' . $this->_cnonce . ':' . $this->_digestchallenge['qop'] . ':' . md5($a2)) . '"'; + } + + $response = array(); + foreach ($signature as $key => $value) { + $response[] = "$key=$value"; + } + return 'Authorization: Digest ' . implode(', ', $response); + } + /** * Private method send_request * @@ -1373,7 +1470,10 @@ EOD; // Therefore we need to reopen the socket, before are sending the next request... $this->_error_log('Connection: close found'); $this->_connection_closed = true; + } else if (preg_match('@^HTTP/1\.(1|0) 401 @', $header)) { + $this->_error_log('The server requires an authentication'); } + // check how to get the data on socket stream // chunked or content-length (HTTP/1.1) or // one block until feof is received (HTTP/1.0) @@ -1472,6 +1572,7 @@ EOD; // $this->_buffer = $header . "\r\n\r\n" . $buffer; $this->_error_log($this->_header); $this->_error_log($this->_body); + } diff --git a/repository/webdav/lib.php b/repository/webdav/lib.php index 07c66b1eab6..51826c8dbc8 100644 --- a/repository/webdav/lib.php +++ b/repository/webdav/lib.php @@ -166,7 +166,7 @@ class repository_webdav extends repository { $choices = array(); $choices['none'] = get_string('none'); $choices['basic'] = get_string('webdavbasicauth', 'repository_webdav'); - //$choices['digest'] = get_string('webdavdigestauth', 'repository_webdav'); + $choices['digest'] = get_string('webdavdigestauth', 'repository_webdav'); $mform->addElement('select', 'webdav_auth', get_string('authentication', 'admin'), $choices); $mform->addRule('webdav_auth', get_string('required'), 'required', null, 'client');