module ActionController::HttpAuthentication::Digest

def authenticate(request, realm, &password_procedure)

Returns true on a valid response, false otherwise.
def authenticate(request, realm, &password_procedure)
  request.authorization && validate_digest_response(request, realm, &password_procedure)
end

def authentication_header(controller, realm)

def authentication_header(controller, realm)
  secret_key = secret_token(controller.request)
  nonce = self.nonce(secret_key)
  opaque = opaque(secret_key)
  controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
end

def authentication_request(controller, realm, message = nil)

def authentication_request(controller, realm, message = nil)
  message ||= "HTTP Digest: Access denied.\n"
  authentication_header(controller, realm)
  controller.status = 401
  controller.response_body = message
end

def decode_credentials(header)

def decode_credentials(header)
  ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
    key, value = pair.split("=", 2)
    [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")]
  end]
end

def decode_credentials_header(request)

def decode_credentials_header(request)
  decode_credentials(request.authorization)
end

def encode_credentials(http_method, credentials, password, password_is_ha1)

def encode_credentials(http_method, credentials, password, password_is_ha1)
  credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
  "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
end

def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)

ha1 digest instead of a plain-text password.
`password_is_ha1` is set to `true` by default, since best practice is to store
decoded `credentials` and the expected `password` Optional parameter
Returns the expected response for a request of `http_method` to `uri` with the
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
  ha1 = password_is_ha1 ? password : ha1(credentials, password)
  ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
  OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
end

def ha1(credentials, password)

def ha1(credentials, password)
  OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":"))
end

def nonce(secret_key, time = Time.now)

Ensures the time cannot be modified by client.
secret key from the Rails session secret generated upon creation of project.
The nonce is opaque to the client. Composed of Time, and hash of Time with

issues involved see Section 4 of this document.
or PATCH requests, and a time-stamp for GET requests. For more details on the
implementation might choose to use one-time nonces or digests for POST, PUT,
previously used digest, in order to protect against a replay attack. Or, an
An implementation might choose not to accept a previously used nonce or a

spoofing is not that hard.)
a single user often go through different proxies in the farm. Also, IP address
originally got it. However, that would break proxy farms, where requests from
server the ability to limit the reuse of the nonce to the same client that
including the IP address of the client in the nonce would appear to offer the
ETag prevents a replay request for an updated version of the resource. (Note:
the server can limit the time of the nonce's validity. The inclusion of the
from that header or if the time-stamp value is not recent enough. In this way
authentication header and reject the request if it did not match the nonce
server would recalculate the hash portion after receiving the client
private-key is data known only to the server. With a nonce of this form a
is the value of the HTTP ETag header associated with the requested entity, and
where time-stamp is a server-generated time or other non-repeating value, ETag

time-stamp H(time-stamp ":" ETag ":" private-key)

constructed as the base 64 encoding of
implementation depends on a good choice. A nonce might, for example, be
The contents of the nonce are implementation dependent. The quality of the

as a quoted string, the double-quote character is not allowed.
hexadecimal data. Specifically, since the string is passed in the header lines
401 response is made. It is recommended that this string be base64 or
A server-specified data string which should be uniquely generated each time a

Uses an MD5 digest based on time to generate a value to be used only once.
def nonce(secret_key, time = Time.now)
  t = time.to_i
  hashed = [t, secret_key]
  digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":"))
  ::Base64.strict_encode64("#{t}:#{digest}")
end

def opaque(secret_key)

Opaque based on digest of secret key
def opaque(secret_key)
  OpenSSL::Digest::MD5.hexdigest(secret_key)
end

def secret_token(request)

def secret_token(request)
  key_generator  = request.key_generator
  http_auth_salt = request.http_auth_salt
  key_generator.generate_key(http_auth_salt)
end

def validate_digest_response(request, realm, &password_procedure)

fails, then try it as a plain text password.
expected value. First try the password as a ha1 digest password. If this
Returns false unless the request credentials response value matches the
def validate_digest_response(request, realm, &password_procedure)
  secret_key  = secret_token(request)
  credentials = decode_credentials_header(request)
  valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])
  if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
    password = password_procedure.call(credentials[:username])
    return false unless password
    method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD")
    uri    = credentials[:uri]
    [true, false].any? do |trailing_question_mark|
      [true, false].any? do |password_is_ha1|
        _uri = trailing_question_mark ? uri + "?" : uri
        expected = expected_response(method, _uri, credentials, password, password_is_ha1)
        expected == credentials[:response]
      end
    end
  end
end

def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)

without prompting the user again for their username and password.
the Stale directive is implemented. This would allow a user to use new nonce
or POST, and if the client is a browser or web service. Can be much shorter if
Might want a shorter timeout depending on whether the request is a PATCH, PUT,
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
  return false if value.nil?
  t = ::Base64.decode64(value).split(":").first.to_i
  nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end