Skip to content

Instantly share code, notes, and snippets.

@elhu
Created July 30, 2012 16:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elhu/3208072 to your computer and use it in GitHub Desktop.
Save elhu/3208072 to your computer and use it in GitHub Desktop.
em-http-request HTTP Digest Auth middleware
module EventMachine
module Middleware
require 'digest'
require 'securerandom'
require 'cgi'
class DigestAuth
def initialize(opts = {})
@nonce_count = -1
@opts = {}
opts.each {|k, v| @opts[k.to_sym] = v}
@digest_params = {
algorithm: 'MD5'
}
end
def request(client, head, body)
request = client.req
Fiber.new do
http = EM::HttpRequest.new(request.uri).get
http.callback {
get_params(http.response_header['WWW_AUTHENTICATE'])
head['Authorization'] = build_auth_digest(request)
}
end.resume
[head, body]
end
private
def build_auth_digest(request)
nonce_count = next_nonce
user = CGI.unescape @opts[:username]
password = CGI.unescape @opts[:password]
splitted_algorithm = @digest_params[:algorithm].split('-')
sess = splitted_algorithm[1]
raw_algorithm = splitted_algorithm[0]
if %w(MD5 SHA1 SHA2 SHA256 SHA384 SHA512 RMD160).include? raw_algorithm
algorithm = eval("Digest::#{raw_algorithm}")
else
raise Error, "unknown algorithm: #{raw_algorithm}"
end
qop = @digest_params[:qop]
cnonce = make_cnonce if qop or sess
a1 = if sess
[
algorithm.hexdigest("#{@opts[:username]}:#{@digest_params[:realm]}:#{@opts[:password]}"),
@digest_params[:nonce],
cnonce,
].join ':'
else
"#{@opts[:username]}:#{@digest_params[:realm]}:#{@opts[:password]}"
end
ha1 = algorithm.hexdigest a1
ha2 = algorithm.hexdigest "#{request.method}:#{request.uri}"
request_digest = [ha1, @digest_params[:nonce]]
request_digest.push(('%08x' % @nonce_count), cnonce, qop) if qop
request_digest << ha2
request_digest = request_digest.join ':'
header = [
"Digest username=\"#{@opts[:username]}\"",
"realm=\"#{@digest_params[:realm]}\"",
"algorithm=#{raw_algorithm}",
"uri=\"#{request.uri}\"",
"nonce=\"#{@digest_params[:nonce]}\"",
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
]
if @digest_params[:qop]
header << "qop=#{qop}"
header << "nc=#{'%08x' % @nonce_count}"
header << "cnonce=\"#{cnonce}\""
end
header << "opaque=\"@digest_params[:opaque]\"" if @digest_params.key? :opaque
header.join(', ')
end
def get_params(www_authenticate)
chunks = www_authenticate.split(' ')
method = chunks[0]
if method == 'Digest'
chunks.shift
chunks.each do |chunk|
splitted_chunk = chunk.split('=')
@digest_params[splitted_chunk[0].to_sym] = splitted_chunk[1][1..-3]
end
end
end
def make_cnonce
Digest::MD5.hexdigest [
Time.now.to_i,
$$,
SecureRandom.random_number(2**32),
].join ':'
end
def next_nonce
@nonce_count += 1
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment