aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/http_authentication.rb
blob: 78f41eb5030876f0da28caa67886da5d7725418f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require 'base64'

module ActionController
  module HttpAuthentication
    # Makes it dead easy to do HTTP Basic authentication.
    # 
    # Simple Basic example:
    # 
    #   class PostsController < ApplicationController
    #     USER_NAME, PASSWORD = "dhh", "secret"
    #   
    #     before_filter :authenticate, :except => [ :index ]
    #   
    #     def index
    #       render :text => "Everyone can see me!"
    #     end
    #   
    #     def edit
    #       render :text => "I'm only accessible if you know the password"
    #     end
    #   
    #     private
    #       def authenticate
    #         authenticate_or_request_with_http_basic do |user_name, password| 
    #           user_name == USER_NAME && 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, 
    # the regular HTML interface is protected by a session approach:
    # 
    #   class ApplicationController < 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
    #           if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
    #             @current_user = user
    #           else
    #             request_http_basic_authentication
    #           end
    #         else
    #           if session_authenticated?
    #             @current_user = @account.users.find(session[:authenticated][:user_id])
    #           else
    #             redirect_to(login_url) and return false
    #           end
    #         end
    #       end
    #   end
    # 
    # 
    # In your integration tests, you can do something like this:
    # 
    #   def test_access_granted_from_xml
    #     get(
    #       "/notes/1.xml", nil, 
    #       :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
    #     )
    # 
    #     assert_equal 200, status
    #   end
    #  
    #  
    # 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 public/.htaccess (replace the plain one):
    # 
    #   RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
    module Basic
      extend self

      module ControllerMethods
        def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
          authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
        end

        def authenticate_with_http_basic(&login_procedure)
          HttpAuthentication::Basic.authenticate(self, &login_procedure)
        end

        def request_http_basic_authentication(realm = "Application")
          HttpAuthentication::Basic.authentication_request(self, realm)
        end
      end

      def authenticate(controller, &login_procedure)
        if authorization(controller.request).blank?
          false
        else
          login_procedure.call(*user_name_and_password(controller.request))
        end
      end

      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)
        Base64.decode64(authorization(request).split.last || '')
      end

      def encode_credentials(user_name, password)
        "Basic #{Base64.encode64("#{user_name}:#{password}")}"
      end

      def authentication_request(controller, realm)
        controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
        controller.send :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
        return false    
      end
    end
  end
end