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