From eca80d8538c180ac5e3965cf5f74dbd344e3eeee Mon Sep 17 00:00:00 2001 From: "Ismael MOUSSA S. (T0194673)" Date: Mon, 3 May 2021 03:00:18 +0200 Subject: [PATCH] Repairing digest authentication in handle_request #19 --- lib/net/dav.rb | 114 ++++++++---------------------------------------- net_dav.gemspec | 9 ++++ 2 files changed, 28 insertions(+), 95 deletions(-) diff --git a/lib/net/dav.rb b/lib/net/dav.rb index be87571..3db72dd 100644 --- a/lib/net/dav.rb +++ b/lib/net/dav.rb @@ -1,11 +1,9 @@ require 'net/https' require 'uri' require 'nokogiri' -require File.dirname(__FILE__) + "/dav/item" +require 'net/dav/item' require 'base64' require 'digest/md5' -require File.dirname(__FILE__) + "/dav/version" - begin require 'curb' rescue LoadError @@ -159,7 +157,11 @@ def handle_request(req, headers, limit = MAX_REDIRECTS, &block) when :basic req.basic_auth @user, @pass when :digest - digest_auth(req, @user, @pass, response) + uri = @uri.merge(req.path) + uri.user = @user + uri.password = @pass + # re-calculate the digest header with the current uri path (and increment the nc) + req['Authorization'] = @digest_info.auth_header(uri, @lastServerDigestHeader, req.method) end response = nil @@ -179,15 +181,18 @@ def handle_request(req, headers, limit = MAX_REDIRECTS, &block) return response when Net::HTTPUnauthorized then response.error! unless @user - response.error! if req['authorization'] new_req = clone_req(req.path, req, headers) - if response['www-authenticate'] =~ /^basic/i + if response['www-authenticate'] =~ /^Basic/ if disable_basic_auth raise "server requested basic auth, but that is disabled" end + response.error! if req['authorization'] @authorization = :basic else @authorization = :digest + # Need to set up a new digest auth. + # Either we never had one, or the server wants a fresh one calculated. + digest_auth(req, @user, @pass, response) end return handle_request(req, headers, limit - 1, &block) when Net::HTTPRedirection then @@ -213,66 +218,22 @@ def clone_req(path, req, headers) end new_req.content_length = req.content_length if req.content_length headers.each_pair { |key, value| new_req[key] = value } if headers - new_req.content_type = req.content_type if req.content_type return new_req end - CNONCE = Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))).slice(0, 8) def digest_auth(request, user, password, response) - # based on http://segment7.net/projects/ruby/snippets/digest_auth.rb - @nonce_count = 0 if @nonce_count.nil? - @nonce_count += 1 raise "bad www-authenticate header" unless (response['www-authenticate'] =~ /^(\w+) (.*)/) - params = {} - $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } - - a_1 = "#{user}:#{params['realm']}:#{password}" - a_2 = "#{request.method}:#{request.path}" - request_digest = '' - request_digest << Digest::MD5.hexdigest(a_1) - request_digest << ':' << params['nonce'] - request_digest << ':' << ('%08x' % @nonce_count) - request_digest << ':' << CNONCE - request_digest << ':' << params['qop'] - request_digest << ':' << Digest::MD5.hexdigest(a_2) - - header = [] - header << "Digest username=\"#{user}\"" - header << "realm=\"#{params['realm']}\"" - header << "nonce=\"#{params['nonce']}\"" - header << "uri=\"#{request.path}\"" - header << "cnonce=\"#{CNONCE}\"" - header << "nc=#{'%08x' % @nonce_count}" - header << "qop=#{params['qop']}" - header << "response=\"#{Digest::MD5.hexdigest(request_digest)}\"" - header << "algorithm=\"MD5\"" - - header = header.join(', ') - request['Authorization'] = header - end - - def cert_file(cert_file) - # expects a OpenSSL::X509::Certificate object as client certificate - @http.cert = OpenSSL::X509::Certificate.new(File.read(cert_file)) - #puts @http.cert.not_after - #puts @http.cert.subject - end + @digest_info = Net::HTTP::DigestAuth.new + uri = @uri + uri.user = user + uri.password = password + request['Authorization'] = @digest_info.auth_header(uri, response['www-authenticate'], request.method) - def cert_key(cert_file, cert_file_password) - # expects a OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object - if cert_file_password then - @http.key = OpenSSL::PKey::RSA.new(File.read(cert_file),cert_file_password) - else - @http.key = OpenSSL::PKey::RSA.new(File.read(cert_file)) - end - end - - # path of a CA certification file in PEM format. The file can contain several CA certificates. - def ca_file(ca_file) - @http.ca_file = ca_file + # Keep this around so that we can send subsequent requests without hitting HTTPUnauthorized again. + @lastServerDigestHeader = response['www-authenticate'] end end @@ -334,23 +295,6 @@ def request_returning_body(verb, path, headers) end curl.body_str end - - def cert_file(cert_file) - # expects a cert file - @curl.cert = cert_file - end - - def cert_key(cert_file, cert_file_password) - if cert_file_password then - @curl.certpassword = cert_file_password - end - @curl.key = cert_key - end - - def ca_file(ca_file) - # path of a cacert bundle for this instance. This file will be used to validate SSL certificates. - @curl.cacert = ca_file - end end @@ -439,33 +383,13 @@ def start(&block) # :yield: dav def credentials(user, pass) @handler.user = user @handler.pass = pass - - # Return something explicitly since this command might be run in a - # console where the last statement would be printed. - nil - end - - # Set credentials for ssl certificate authentication - def ssl_certificate(cert_file, *cert_file_password) - @handler.cert_file(cert_file) - @handler.cert_key(cert_file, cert_file_password) - - # Return something explicitly since this command might be run in a - # console where the last statement would be printed. - nil - end - - # Set additional ssl authorities for ssl certificate authentication - def ssl_authority(ca_file) - @handler.ca_file(ca_file) - nil end # Set extra headers for the dav request def headers(headers) @headers = headers end - + # Perform a PROPFIND request # # Example: diff --git a/net_dav.gemspec b/net_dav.gemspec index fd75807..7299595 100644 --- a/net_dav.gemspec +++ b/net_dav.gemspec @@ -13,6 +13,14 @@ Gem::Specification.new do |spec| spec.description = "WebDAV client library in the style of Net::HTTP, using Net::HTTP and libcurl, if installed" spec.homepage = "https://github.com/devrandom/net_dav" + # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' + # to allow pushing to a single host or delete this section to allow pushing to any host. + # if spec.respond_to?(:metadata) + # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" + # else + # raise "RubyGems 2.0 or newer is required to protect against public gem pushes." + # end + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } @@ -24,6 +32,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rspec" spec.add_dependency "nokogiri" + spec.add_dependency "net-http-digest_auth", ">=1.2"