<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -68,8 +68,11 @@ module ActionController
     #
     # Simple Digest example:
     #
+    #   require 'digest/md5'
     #   class PostsController &lt; ApplicationController
-    #     USERS = {&quot;dhh&quot; =&gt; &quot;secret&quot;}
+    #     REALM = &quot;SuperSecret&quot;
+    #     USERS = {&quot;dhh&quot; =&gt; &quot;secret&quot;, #plain text password
+    #              &quot;dap&quot; =&gt; Digest:MD5::hexdigest([&quot;dap&quot;,REALM,&quot;secret&quot;].join(&quot;:&quot;))  #ha1 digest password
     #
     #     before_filter :authenticate, :except =&gt; [:index]
     #
@@ -83,14 +86,18 @@ module ActionController
     #
     #     private
     #       def authenticate
-    #         authenticate_or_request_with_http_digest(realm) do |username|
+    #         authenticate_or_request_with_http_digest(REALM) do |username|
     #           USERS[username]
     #         end
     #       end
     #   end
     #
-    # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately
-    #       hash it to check the user's credentials. Returning +nil+ will cause authentication to fail.
+    # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately
+    #       hash to check the user's credentials. Returning +nil+ will cause authentication to fail.
+    #       Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
+    #       the password file or database is compromised, the attacker would be able to use the ha1 hash to
+    #       authenticate as the user at this +realm+, but would not have the user's password to try using at
+    #       other sites.
     #
     # On shared hosts, Apache sometimes doesn't pass authentication headers to
     # FCGI instances. If your environment matches this description and you cannot
@@ -177,26 +184,37 @@ module ActionController
       end
 
       # Raises error unless the request credentials response value matches the expected value.
+      # First try the password as a ha1 digest password. If this fails, then try it as a plain
+      # text password.
       def validate_digest_response(request, realm, &amp;password_procedure)
         credentials = decode_credentials_header(request)
         valid_nonce = validate_nonce(request, credentials[:nonce])
 
-        if valid_nonce &amp;&amp; realm == credentials[:realm] &amp;&amp; opaque(request.session.session_id) == credentials[:opaque]
+        if valid_nonce &amp;&amp; realm == credentials[:realm] &amp;&amp; opaque == credentials[:opaque]
           password = password_procedure.call(credentials[:username])
-          expected = expected_response(request.env['REQUEST_METHOD'], credentials[:uri], credentials, password)
-          expected == credentials[:response]
+
+         [true, false].any? do |password_is_ha1|
+           expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1)
+           expected == credentials[:response]
+         end
         end
       end
 
       # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
-      def expected_response(http_method, uri, credentials, password)
-        ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+      # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
+      # of a plain-text password.
+      def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
+        ha1 = password_is_ha1 ? password : ha1(credentials, password)
         ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
         ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
       end
 
-      def encode_credentials(http_method, credentials, password)
-        credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password)
+      def ha1(credentials, password)
+        ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
+      end
+
+      def encode_credentials(http_method, credentials, password, password_is_ha1)
+        credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
         &quot;Digest &quot; + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a &lt;&lt; &quot;#{v[0]}='#{v[1]}'&quot; }.join(', ')
       end
 
@@ -213,8 +231,7 @@ module ActionController
       end
 
       def authentication_header(controller, realm)
-        session_id = controller.request.session.session_id
-        controller.headers[&quot;WWW-Authenticate&quot;] = %(Digest realm=&quot;#{realm}&quot;, qop=&quot;auth&quot;, algorithm=MD5, nonce=&quot;#{nonce(session_id)}&quot;, opaque=&quot;#{opaque(session_id)}&quot;)
+        controller.headers[&quot;WWW-Authenticate&quot;] = %(Digest realm=&quot;#{realm}&quot;, qop=&quot;auth&quot;, algorithm=MD5, nonce=&quot;#{nonce}&quot;, opaque=&quot;#{opaque}&quot;)
       end
 
       def authentication_request(controller, realm, message = nil)
@@ -252,23 +269,36 @@ module ActionController
       # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
       # of this document.
       #
-      # The nonce is opaque to the client.
-      def nonce(session_id, time = Time.now)
+      # The nonce is opaque to the client. Composed of Time, and hash of Time with secret
+      # key from the Rails session secret generated upon creation of project. Ensures
+      # the time cannot be modifed by client.
+      def nonce(time = Time.now)
         t = time.to_i
-        hashed = [t, session_id]
+        hashed = [t, secret_key]
         digest = ::Digest::MD5.hexdigest(hashed.join(&quot;:&quot;))
         Base64.encode64(&quot;#{t}:#{digest}&quot;).gsub(&quot;\n&quot;, '')
       end
 
-      def validate_nonce(request, value)
+      # Might want a shorter timeout depending on whether the request
+      # is a PUT or POST, and if client is browser or web service.
+      # Can be much shorter if the Stale directive is implemented. This would
+      # allow a user to use new nonce without prompting user again for their
+      # username and password.
+      def validate_nonce(request, value, seconds_to_timeout=5*60)
         t = Base64.decode64(value).split(&quot;:&quot;).first.to_i
-        nonce(request.session.session_id, t) == value &amp;&amp; (t - Time.now.to_i).abs &lt;= 10 * 60
+        nonce(t) == value &amp;&amp; (t - Time.now.to_i).abs &lt;= seconds_to_timeout
       end
 
-      # Opaque based on digest of session_id
-      def opaque(session_id)
-        Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub(&quot;\n&quot;, '')
+      # Opaque based on random generation - but changing each request?
+      def opaque()
+        ::Digest::MD5.hexdigest(secret_key)
       end
+
+      # Set in /initializers/session_store.rb, and loaded even if sessions are not in use.
+      def secret_key
+        ActionController::Base.session_options[:secret]
+      end
+
     end
   end
 end</diff>
      <filename>actionpack/lib/action_controller/http_authentication.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,7 +5,8 @@ class HttpDigestAuthenticationTest &lt; ActionController::TestCase
     before_filter :authenticate, :only =&gt; :index
     before_filter :authenticate_with_request, :only =&gt; :display
 
-    USERS = { 'lifo' =&gt; 'world', 'pretty' =&gt; 'please' }
+    USERS = { 'lifo' =&gt; 'world', 'pretty' =&gt; 'please',
+              'dhh' =&gt; ::Digest::MD5::hexdigest([&quot;dhh&quot;,&quot;SuperSecret&quot;,&quot;secret&quot;].join(&quot;:&quot;))}
 
     def index
       render :text =&gt; &quot;Hello Secret&quot;
@@ -107,8 +108,42 @@ class HttpDigestAuthenticationTest &lt; ActionController::TestCase
     assert_equal 'Definitely Maybe', @response.body
   end
 
-   test &quot;authentication request with relative URI&quot; do
-    @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri =&gt; &quot;/&quot;, :username =&gt; 'pretty', :password =&gt; 'please')
+  test &quot;authentication request with valid credential and nil session&quot; do
+    @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username =&gt; 'pretty', :password =&gt; 'please')
+
+    # session_id = &quot;&quot; in functional test, but is +nil+ in real life
+    @request.session.session_id = nil
+    get :display
+
+    assert_response :success
+    assert assigns(:logged_in)
+    assert_equal 'Definitely Maybe', @response.body
+  end
+
+   test &quot;authentication request with request-uri that doesn't match credentials digest-uri&quot; do
+    @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username =&gt; 'pretty', :password =&gt; 'please')
+    @request.env['REQUEST_URI'] = &quot;/http_digest_authentication_test/dummy_digest/altered/uri&quot;
+    get :display
+
+    assert_response :unauthorized
+    assert_equal &quot;Authentication Failed&quot;, @response.body
+  end
+
+   test &quot;authentication request with absolute uri&quot; do
+    @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri =&gt; &quot;http://test.host/http_digest_authentication_test/dummy_digest/display&quot;,
+                                                            :username =&gt; 'pretty', :password =&gt; 'please')
+    @request.env['REQUEST_URI'] = &quot;http://test.host/http_digest_authentication_test/dummy_digest/display&quot;
+    get :display
+
+    assert_response :success
+    assert assigns(:logged_in)
+    assert_equal 'Definitely Maybe', @response.body
+  end
+
+  test &quot;authentication request with password stored as ha1 digest hash&quot; do
+    @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username =&gt; 'dhh',
+                                           :password =&gt; ::Digest::MD5::hexdigest([&quot;dhh&quot;,&quot;SuperSecret&quot;,&quot;secret&quot;].join(&quot;:&quot;)),
+                                           :password_is_ha1 =&gt; true)
     get :display
 
     assert_response :success
@@ -119,18 +154,22 @@ class HttpDigestAuthenticationTest &lt; ActionController::TestCase
   private
 
   def encode_credentials(options)
-    options.reverse_merge!(:nc =&gt; &quot;00000001&quot;, :cnonce =&gt; &quot;0a4f113b&quot;)
+    options.reverse_merge!(:nc =&gt; &quot;00000001&quot;, :cnonce =&gt; &quot;0a4f113b&quot;, :password_is_ha1 =&gt; false)
     password = options.delete(:password)
 
-    # Perform unautheticated get to retrieve digest parameters to use on subsequent request
+    # Set in /initializers/session_store.rb. Used as secret in generating nonce
+    # to prevent tampering of timestamp
+    ActionController::Base.session_options[:secret] = &quot;session_options_secret&quot;
+
+    # Perform unauthenticated GET to retrieve digest parameters to use on subsequent request
     get :index
 
     assert_response :unauthorized
 
     credentials = decode_credentials(@response.headers['WWW-Authenticate'])
     credentials.merge!(options)
-    credentials.reverse_merge!(:uri =&gt; &quot;http://#{@request.host}#{@request.env['REQUEST_URI']}&quot;)
-    ActionController::HttpAuthentication::Digest.encode_credentials(&quot;GET&quot;, credentials, password)
+    credentials.reverse_merge!(:uri =&gt; &quot;#{@request.env['REQUEST_URI']}&quot;)
+    ActionController::HttpAuthentication::Digest.encode_credentials(&quot;GET&quot;, credentials, password, options[:password_is_ha1])
   end
 
   def decode_credentials(header)</diff>
      <filename>actionpack/test/controller/http_digest_authentication_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>7b382cb9e5c5706f8d15216159a2873375915c9c</id>
    </parent>
  </parents>
  <author>
    <name>Donald Parish</name>
    <login>Milhouse</login>
    <email>donald.parish@gmail.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/be7b64b35aac1c9e9063d1d8317f8b1be2a3411c</url>
  <id>be7b64b35aac1c9e9063d1d8317f8b1be2a3411c</id>
  <committed-date>2009-03-12T06:24:54-07:00</committed-date>
  <authored-date>2009-03-12T06:24:54-07:00</authored-date>
  <message>Support MD5 passwords for Digest auth and use session_options[:secret] in nonce [#2209 state:resolved]

Signed-off-by: Pratik Naik &lt;pratiknaik@gmail.com&gt;</message>
  <tree>39dc838e8ab9607f3ac838385090a0ce45ac99ee</tree>
  <committer>
    <name>Pratik Naik</name>
    <login>lifo</login>
    <email>pratiknaik@gmail.com</email>
  </committer>
</commit>
