Skip to content

Commit

Permalink
Fix digest auth for unspecified quality of protection (qop)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjoernalbers authored and sandro committed Jan 20, 2011
1 parent 802a4ed commit bb2818f
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 26 deletions.
88 changes: 62 additions & 26 deletions lib/httparty/net_digest_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,69 @@

module Net
module HTTPHeader
def digest_auth(user, password, response)
response['www-authenticate'] =~ /^(\w+) (.*)/

params = {}
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
params.merge!("cnonce" => Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))))

a_1 = Digest::MD5.hexdigest("#{user}:#{params['realm']}:#{password}")
a_2 = Digest::MD5.hexdigest("#{@method}:#{@path}")

request_digest = Digest::MD5.hexdigest(
[a_1, params['nonce'], "0", params['cnonce'], params['qop'], a_2].join(":")
)

header = [
%Q(Digest username="#{user}"),
%Q(realm="#{params['realm']}"),
%Q(qop="#{params['qop']}"),
%Q(uri="#{@path}"),
%Q(nonce="#{params['nonce']}"),
%Q(nc="0"),
%Q(cnonce="#{params['cnonce']}"),
%Q(opaque="#{params['opaque']}"),
%Q(response="#{request_digest}")
]
def digest_auth(username, password, response)
@header['Authorization'] = DigestAuthenticator.new(username, password,
@method, @path, response).authorization_header
end


class DigestAuthenticator
def initialize(username, password, method, path, response_header)
@username = username
@password = password
@method = method
@path = path
@response = parse(response_header)
end

def authorization_header
@cnonce = md5(random)
header = [%Q(Digest username="#{@username}"),
%Q(realm="#{@response['realm']}"),
%Q(nonce="#{@response['nonce']}"),
%Q(uri="#{@path}"),
%Q(response="#{request_digest}")]
[%Q(cnonce="#{@cnonce}"),
%Q(opaque="#{@response['opaque']}"),
%Q(qop="#{@response['qop']}"),
%Q(nc="0")].each { |field| header << field } if qop_present?
header
end

private

def parse(response_header)
response_header['www-authenticate'] =~ /^(\w+) (.*)/
params = {}
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
params
end

def qop_present?
@response.has_key?('qop') and not @response['qop'].empty?
end

def random
"%x" % (Time.now.to_i + rand(65535))
end

def request_digest
a = [md5(a1), @response['nonce'], md5(a2)]
a.insert(2, "0", @cnonce, @response['qop']) if qop_present?
md5(a.join(":"))
end

def md5(str)
Digest::MD5.hexdigest(str)
end

def a1
[@username, @response['realm'], @password].join(":")
end

@header['Authorization'] = header
def a2
[@method, @path].join(":")
end
end
end
end
93 changes: 93 additions & 0 deletions spec/httparty/net_digest_auth_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))

describe Net::HTTPHeader::DigestAuthenticator do
def setup_digest(response)
digest = Net::HTTPHeader::DigestAuthenticator.new("Mufasa",
"Circle Of Life", "GET", "/dir/index.html", response)
digest.stub(:random).and_return("deadbeef")
Digest::MD5.stub(:hexdigest) { |str| "md5(#{str})" }
digest
end

def authorization_header
@digest.authorization_header.join(", ")
end


context "with specified quality of protection (qop)" do
before do
@digest = setup_digest({'www-authenticate' =>
'Digest realm="myhost@testrealm.com", nonce="NONCE", qop="auth"'})
end

it "should set prefix" do
authorization_header.should =~ /^Digest /
end

it "should set username" do
authorization_header.should include(%Q(username="Mufasa"))
end

it "should set digest-uri" do
authorization_header.should include(%Q(uri="/dir/index.html"))
end

it "should set qop" do
authorization_header.should include(%Q(qop="auth"))
end

it "should set cnonce" do
authorization_header.should include(%Q(cnonce="md5(deadbeef)"))
end

it "should set nonce-count" do
authorization_header.should include(%Q(nc="0"))
end

it "should set response" do
request_digest =
"md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life)" +
":NONCE:0:md5(deadbeef):auth:md5(GET:/dir/index.html))"
authorization_header.should include(%Q(response="#{request_digest}"))
end
end


context "with unspecified quality of protection (qop)" do
before do
@digest = setup_digest({'www-authenticate' =>
'Digest realm="myhost@testrealm.com", nonce="NONCE"'})
end

it "should set prefix" do
authorization_header.should =~ /^Digest /
end

it "should set username" do
authorization_header.should include(%Q(username="Mufasa"))
end

it "should set digest-uri" do
authorization_header.should include(%Q(uri="/dir/index.html"))
end

it "should not set qop" do
authorization_header.should_not include(%Q(qop=))
end

it "should not set cnonce" do
authorization_header.should_not include(%Q(cnonce=))
end

it "should not set nonce-count" do
authorization_header.should_not include(%Q(nc=))
end

it "should set response" do
request_digest =
"md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life)" +
":NONCE:md5(GET:/dir/index.html))"
authorization_header.should include(%Q(response="#{request_digest}"))
end
end
end

0 comments on commit bb2818f

Please sign in to comment.