Utils contains all sort of conveniance methods for the header container classes. Implementations shouldn't have to call any methods on Utils.
Calculate the digest value for the directives as explained in the RFC.
variant
: Either :request
or
:response
, as seen from the server.
# File lib/httpauth/digest.rb, line 188 def calculate_digest(h, s, variant) raise ArgumentError.new("Variant should be either :request or :response, not #{variant}") unless [:request, :response].include?(variant) # Compatability with RFC 2069 if h[:qop].nil? digest_kd digest_a1(h, s), digest_concat( h[:nonce], send("#{variant}_digest_a2".intern, h) ) else digest_kd digest_a1(h, s), digest_concat( h[:nonce], Conversions.int_to_hex(h[:nc]), h[:cnonce], h[:qop], send("#{variant}_digest_a2".intern, h) ) end end
Create a nonce value of the time and a salt. The nonce is created in such a way that the issuer can check the age of the nonce.
salt
: A reasonably long passphrase known only to the issuer.
# File lib/httpauth/digest.rb, line 221 def create_nonce(salt) now = Time.now time = now.strftime("%Y-%m-%d %H:%M:%S").to_s + ':' + now.usec.to_s Base64.encode64( digest_concat( time, digest_h(digest_concat(time, salt)) ) ).gsub("\n", '')[0..-3] end
Create a 32 character long opaque string with a ‘random’ value
# File lib/httpauth/digest.rb, line 233 def create_opaque s = []; 16.times { s << rand(127).chr } digest_h s.join end
Decodes digest directives from a header. Returns a hash with directives.
directives
: The directives
variant
: Specifies whether the directives are for an Authorize
header (:credentials), for a WWW-Authenticate header (:challenge) or for a
Authentication-Info header (:auth_info).
# File lib/httpauth/digest.rb, line 89 def decode_directives(directives, variant) raise HTTPAuth::UnwellformedHeader.new("Can't decode directives which are nil") if directives.nil? decode = {:domain => :space_quoted_string_to_list, :algorithm => false, :stale => :str_to_bool, :nc => :hex_to_int} if [:credentials, :auth].include? variant decode.merge! :qop => false elsif variant == :challenge decode.merge! :qop => :comma_quoted_string_to_list else raise ArgumentError.new("#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge") end start = 0 unless variant == :auth # The first six characters are 'Digest ' start = 6 scheme = directives[0..6].strip raise HTTPAuth::UnwellformedHeader.new("Scheme should be Digest, server responded with `#{directives}'") unless scheme == 'Digest' end # The rest are the directives # TODO: split is ugly, I want a real parser (: directives[start..-1].split(',').inject({}) do |h,part| parts = part.split('=') name = parts[0].strip.intern value = parts[1..-1].join('=').strip # --- HACK # IE and Safari qoute qop values # IE also quotes algorithm values if variant != :challenge and [:qop, :algorithm].include?(name) and value =~ %r^\"[^\"]+\"$/ value = Conversions.unquote_string(value) end # --- END HACK if decode[name] h[name] = Conversions.send decode[name], value elsif decode[name].nil? h[name] = Conversions.unquote_string value else h[name] = value end h end end
Calculate the H(A1) as explain in the RFC. If h is set, it’s used instead of calculating H(username “:” realm “:” password).
# File lib/httpauth/digest.rb, line 153 def digest_a1(h, s) # TODO: check for known algorithm values (look out for the IE algorithm quote bug) if h[:algorithm] == 'MD5-sess' digest_h digest_concat( h[:digest] || htdigest(h[:username], h[:realm], h[:password]), h[:nonce], h[:cnonce] ) else h[:digest] || htdigest(h[:username], h[:realm], h[:password]) end end
Concat arguments the way it’s done frequently in the Digest spec.
digest_concat('a', 'b') #=> "a:b" digest_concat('a', 'b', c') #=> "a:b:c"
# File lib/httpauth/digest.rb, line 138 def digest_concat(*args); args.join ':'; end
Calculate the MD5 hexdigest for the string data
# File lib/httpauth/digest.rb, line 141 def digest_h(data); ::Digest::MD5.hexdigest data; end
Calculate the KD value of a secret and data as explained in the RFC.
# File lib/httpauth/digest.rb, line 144 def digest_kd(secret, data); digest_h digest_concat(secret, data); end
Encodes a hash with digest directives to send in a header.
h
: The directives specified in a hash
variant
: Specifies whether the directives are for an Authorize
header (:credentials), for a WWW-Authenticate header (:challenge) or for a
Authentication-Info header (:auth_info).
# File lib/httpauth/digest.rb, line 56 def encode_directives(h, variant) encode = {:domain => :list_to_space_quoted_string, :algorithm => false, :stale => :bool_to_str, :nc => :int_to_hex} if [:credentials, :auth].include? variant encode.merge! :qop => false elsif variant == :challenge encode.merge! :qop => :list_to_comma_quoted_string else raise ArgumentError.new("#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge") end (variant == :auth ? '' : 'Digest ') + h.collect do |directive, value| '' << directive.to_s << '=' << if encode[directive] begin Conversions.send encode[directive], value rescue NoMethodError, ArgumentError raise ArgumentError.new("Can't encode #{directive}(#{value.inspect}) with #{encode[directive]}") end elsif encode[directive].nil? begin Conversions.quote_string value rescue NoMethodError, ArgumentError raise ArgumentError.new("Can't encode #{directive}(#{value.inspect}) with quote_string") end else value end end.join(", ") end
Return a hash with the keys in keys
found in h
.
Example
filter_h_on({1=>1,2=>2}, [1]) #=> {1=>1} filter_h_on({1=>1,2=>2}, [1, 2]) #=> {1=>1,2=>2}
# File lib/httpauth/digest.rb, line 213 def filter_h_on(h, keys) h.inject({}) { |r,l| keys.include?(l[0]) ? r.merge({l[0]=>l[1]}) : r } end
Calculate the Digest for the credentials
# File lib/httpauth/digest.rb, line 147 def htdigest(username, realm, password) digest_h digest_concat(username, realm, password) end
Calculate the H(A2) for the Authorize header as explained in the RFC.
# File lib/httpauth/digest.rb, line 167 def request_digest_a2(h) # TODO: check for known qop values (look out for the safari qop quote bug) if h[:qop] == 'auth-int' digest_h digest_concat(h[:method], h[:uri], digest_h(h[:request_body])) else digest_h digest_concat(h[:method], h[:uri]) end end
Calculate the H(A2) for the Authentication-Info header as explained in the RFC.
# File lib/httpauth/digest.rb, line 177 def response_digest_a2(h) if h[:qop] == 'auth-int' digest_h ':' + digest_concat(h[:uri], digest_h(h[:response_body])) else digest_h ':' + h[:uri] end end