lib/httpx/plugins/digest_authentication.rb in httpx-0.3.1 vs lib/httpx/plugins/digest_authentication.rb in httpx-0.4.0

- old
+ new

@@ -1,50 +1,68 @@ # frozen_string_literal: true module HTTPX module Plugins + # + # This plugin adds helper methods to implement HTTP Digest Auth + # https://tools.ietf.org/html/rfc7616 + # module DigestAuthentication DigestError = Class.new(Error) + def self.extra_options(options) + Class.new(options.class) do + def_option(:digest) do |digest| + raise Error, ":digest must be a Digest" unless digest.is_a?(Digest) + + digest + end + end.new(options) + end + def self.load_dependencies(*) require "securerandom" require "digest" end module InstanceMethods def digest_authentication(user, password) - @_digest = Digest.new(user, password) - self + branch(default_options.with_digest(Digest.new(user, password))) end + alias_method :digest_auth, :digest_authentication - def request(*args, keep_open: @keep_open, **options) - return super unless @_digest - begin - requests = __build_reqs(*args, **options) - probe_request = requests.first - prev_response = __send_reqs(*probe_request).first + def request(*args, **options) + requests = build_requests(*args, options) + probe_request = requests.first + digest = probe_request.options.digest - unless prev_response.status == 401 - raise Error, "request doesn't require authentication (status: #{prev_response})" - end + return super unless digest - probe_request.transition(:idle) - responses = [] + prev_response = wrap { send_requests(*probe_request, options).first } - requests.each do |request| - token = @_digest.generate_header(request, prev_response) - request.headers["authorization"] = "Digest #{token}" - response = __send_reqs(*request).first - responses << response - prev_response = response + raise Error, "request doesn't require authentication (status: #{prev_response.status})" unless prev_response.status == 401 + + probe_request.transition(:idle) + + responses = [] + + while (request = requests.shift) + token = digest.generate_header(request, prev_response) + request.headers["authorization"] = "Digest #{token}" + response = if requests.empty? + send_requests(*request, options).first + else + wrap { send_requests(*request, options).first } end - return responses.first if responses.size == 1 - responses - ensure - close unless keep_open + responses << response + prev_response = response end + + return responses.first if responses.size == 1 + + responses end end class Digest def initialize(user, password) @@ -52,14 +70,14 @@ @password = password @nonce = 0 end def generate_header(request, response, _iis = false) - method = request.verb.to_s.upcase + meth = request.verb.to_s.upcase www = response.headers["www-authenticate"] - # TODO: assert if auth-type is Digest + # discard first token, it's Digest auth_info = www[/^(\w+) (.*)/, 2] uri = request.path params = Hash[auth_info.scan(/(\w+)="(.*?)"/)] @@ -98,11 +116,11 @@ else "#{@user}:#{params["realm"]}:#{@password}" end ha1 = algorithm.hexdigest(a1) - ha2 = algorithm.hexdigest("#{method}:#{uri}") + ha2 = algorithm.hexdigest("#{meth}:#{uri}") request_digest = [ha1, nonce] request_digest.push(nc, cnonce, qop) if qop request_digest << ha2 request_digest = request_digest.join(":") @@ -134,8 +152,9 @@ def next_nonce @nonce += 1 end end end + register_plugin :digest_authentication, DigestAuthentication end end