<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>actionpack/test/controller/http_digest_authentication_test.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,26 @@
 *2.3.0 [Edge]*
 
+* Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example :
+
+  class DummyDigestController &lt; ActionController::Base
+    USERS = { &quot;lifo&quot; =&gt; 'world' }
+
+    before_filter :authenticate
+
+    def index
+      render :text =&gt; &quot;Hello Secret&quot;
+    end
+
+    private
+
+    def authenticate
+      authenticate_or_request_with_http_digest(&quot;Super Secret&quot;) do |username|
+        # Return the user's password
+        USERS[username]
+      end
+    end
+  end
+
 * Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly.  #1634 [Yaroslav Markin]
     storage_units:
       # %u is the storage unit, %n is the number (default: 2 MB)</diff>
      <filename>actionpack/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -1344,8 +1344,8 @@ module ActionController #:nodoc:
   Base.class_eval do
     [ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
       Cookies, Caching, Verification, Streaming, SessionManagement,
-      HttpAuthentication::Basic::ControllerMethods, RecordIdentifier,
-      RequestForgeryProtection, Translation
+      HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods,
+      RecordIdentifier, RequestForgeryProtection, Translation
     ].each do |mod|
       include mod
     end</diff>
      <filename>actionpack/lib/action_controller/base.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,42 +1,42 @@
 module ActionController
   module HttpAuthentication
     # Makes it dead easy to do HTTP Basic authentication.
-    # 
+    #
     # Simple Basic example:
-    # 
+    #
     #   class PostsController &lt; ApplicationController
     #     USER_NAME, PASSWORD = &quot;dhh&quot;, &quot;secret&quot;
-    #   
+    #
     #     before_filter :authenticate, :except =&gt; [ :index ]
-    #   
+    #
     #     def index
     #       render :text =&gt; &quot;Everyone can see me!&quot;
     #     end
-    #   
+    #
     #     def edit
     #       render :text =&gt; &quot;I'm only accessible if you know the password&quot;
     #     end
-    #   
+    #
     #     private
     #       def authenticate
-    #         authenticate_or_request_with_http_basic do |user_name, password| 
+    #         authenticate_or_request_with_http_basic do |user_name, password|
     #           user_name == USER_NAME &amp;&amp; password == PASSWORD
     #         end
     #       end
     #   end
-    # 
-    # 
-    # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, 
+    #
+    #
+    # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
     # the regular HTML interface is protected by a session approach:
-    # 
+    #
     #   class ApplicationController &lt; ActionController::Base
     #     before_filter :set_account, :authenticate
-    #   
+    #
     #     protected
     #       def set_account
     #         @account = Account.find_by_url_name(request.subdomains.first)
     #       end
-    #   
+    #
     #       def authenticate
     #         case request.format
     #         when Mime::XML, Mime::ATOM
@@ -54,24 +54,48 @@ module ActionController
     #         end
     #       end
     #   end
-    # 
-    # 
+    #
     # In your integration tests, you can do something like this:
-    # 
+    #
     #   def test_access_granted_from_xml
     #     get(
-    #       &quot;/notes/1.xml&quot;, nil, 
+    #       &quot;/notes/1.xml&quot;, nil,
     #       :authorization =&gt; ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
     #     )
-    # 
+    #
     #     assert_equal 200, status
     #   end
-    #  
-    #  
+    #
+    # Simple Digest example:
+    #
+    #   class PostsController &lt; ApplicationController
+    #     USERS = {&quot;dhh&quot; =&gt; &quot;secret&quot;}
+    #
+    #     before_filter :authenticate, :except =&gt; [:index]
+    #
+    #     def index
+    #       render :text =&gt; &quot;Everyone can see me!&quot;
+    #     end
+    #
+    #     def edit
+    #       render :text =&gt; &quot;I'm only accessible if you know the password&quot;
+    #     end
+    #
+    #     private
+    #       def authenticate
+    #         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.
+    #
     # On shared hosts, Apache sometimes doesn't pass authentication headers to
     # FCGI instances. If your environment matches this description and you cannot
     # authenticate, try this rule in your Apache setup:
-    # 
+    #
     #   RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
     module Basic
       extend self
@@ -99,14 +123,14 @@ module ActionController
       def user_name_and_password(request)
         decode_credentials(request).split(/:/, 2)
       end
-  
+
       def authorization(request)
         request.env['HTTP_AUTHORIZATION']   ||
         request.env['X-HTTP_AUTHORIZATION'] ||
         request.env['X_HTTP_AUTHORIZATION'] ||
         request.env['REDIRECT_X_HTTP_AUTHORIZATION']
       end
-    
+
       def decode_credentials(request)
         ActiveSupport::Base64.decode64(authorization(request).split.last || '')
       end
@@ -120,5 +144,131 @@ module ActionController
         controller.__send__ :render, :text =&gt; &quot;HTTP Basic: Access denied.\n&quot;, :status =&gt; :unauthorized
       end
     end
+
+    module Digest
+      extend self
+
+      module ControllerMethods
+        def authenticate_or_request_with_http_digest(realm = &quot;Application&quot;, &amp;password_procedure)
+          authenticate_with_http_digest(realm, &amp;password_procedure) || request_http_digest_authentication(realm)
+        end
+
+        # Authenticate with HTTP Digest, returns true or false
+        def authenticate_with_http_digest(realm = &quot;Application&quot;, &amp;password_procedure)
+          HttpAuthentication::Digest.authenticate(self, realm, &amp;password_procedure)
+        end
+
+        # Render output including the HTTP Digest authentication header
+        def request_http_digest_authentication(realm = &quot;Application&quot;, message = nil)
+          HttpAuthentication::Digest.authentication_request(self, realm, message)
+        end
+      end
+
+      # Returns false on a valid response, true otherwise
+      def authenticate(controller, realm, &amp;password_procedure)
+        authorization(controller.request) &amp;&amp; validate_digest_response(controller, realm, &amp;password_procedure)
+      end
+
+      def authorization(request)
+        request.env['HTTP_AUTHORIZATION']   ||
+        request.env['X-HTTP_AUTHORIZATION'] ||
+        request.env['X_HTTP_AUTHORIZATION'] ||
+        request.env['REDIRECT_X_HTTP_AUTHORIZATION']
+      end
+
+      # Raises error unless the request credentials response value matches the expected value.
+      def validate_digest_response(controller, realm, &amp;password_procedure)
+        credentials = decode_credentials_header(controller.request)
+        valid_nonce = validate_nonce(controller.request, credentials[:nonce])
+
+        if valid_nonce &amp;&amp; realm == credentials[:realm] &amp;&amp; opaque(controller.request.session.session_id) == credentials[:opaque]
+          password = password_procedure.call(credentials[:username])
+          expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password)
+          expected == credentials[:response]
+        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(':'))
+        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)
+        &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
+
+      def decode_credentials_header(request)
+        decode_credentials(authorization(request))
+      end
+
+      def decode_credentials(header)
+        header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair|
+          key, value = pair.split('=', 2)
+          hash[key.strip.to_sym] = value.to_s.gsub(/^&quot;|&quot;$/,'').gsub(/'/, '')
+          hash
+        end
+      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;)
+      end
+
+      def authentication_request(controller, realm, message = nil)
+        message ||= &quot;HTTP Digest: Access denied.\n&quot;
+        authentication_header(controller, realm)
+        controller.__send__ :render, :text =&gt; message, :status =&gt; :unauthorized
+      end
+
+      # Uses an MD5 digest based on time to generate a value to be used only once.
+      #
+      # A server-specified data string which should be uniquely generated each time a 401 response is made.
+      # It is recommended that this string be base64 or hexadecimal data.
+      # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
+      #
+      # The contents of the nonce are implementation dependent.
+      # The quality of the implementation depends on a good choice.
+      # A nonce might, for example, be constructed as the base 64 encoding of
+      #
+      # =&gt; time-stamp H(time-stamp &quot;:&quot; ETag &quot;:&quot; private-key)
+      #
+      # where time-stamp is a server-generated time or other non-repeating value,
+      # ETag is the value of the HTTP ETag header associated with the requested entity,
+      # and private-key is data known only to the server.
+      # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
+      # reject the request if it did not match the nonce from that header or
+      # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
+      # The inclusion of the ETag prevents a replay request for an updated version of the resource.
+      # (Note: including the IP address of the client in the nonce would appear to offer the server the ability
+      # to limit the reuse of the nonce to the same client that originally got it.
+      # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
+      # Also, IP address spoofing is not that hard.)
+      #
+      # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
+      # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
+      # 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)
+        t = time.to_i
+        hashed = [t, session_id]
+        digest = ::Digest::MD5.hexdigest(hashed.join(&quot;:&quot;))
+        Base64.encode64(&quot;#{t}:#{digest}&quot;).gsub(&quot;\n&quot;, '')
+      end
+
+      def validate_nonce(request, value)
+        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
+      end
+
+      # Opaque based on digest of session_id
+      def opaque(session_id)
+        Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub(&quot;\n&quot;, '')
+      end
+    end
   end
 end</diff>
      <filename>actionpack/lib/action_controller/http_authentication.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>e6493eb9b76de73afef2706493efd090dfff4ecc</id>
    </parent>
  </parents>
  <author>
    <name>Gregg Kellogg</name>
    <login></login>
    <email>gregg@Gregg-Kelloggs-MacBook-Pro-24.local</email>
  </author>
  <url>http://github.com/rails/rails/commit/306cc2b920203cfa51cee82d2fc452484efc72f8</url>
  <id>306cc2b920203cfa51cee82d2fc452484efc72f8</id>
  <committed-date>2009-01-29T08:01:59-08:00</committed-date>
  <authored-date>2009-01-29T08:00:07-08:00</authored-date>
  <message>Implement HTTP Digest authentication. [#1230 state:resolved] [Gregg Kellogg, Pratik Naik]

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