Further repo browser/Dolt integration work
[gitorious:mainline.git] / lib / authenticated_system.rb
1 module AuthenticatedSystem
2   protected
3     # Returns true or false if the user is logged in.
4     # Preloads @current_user with the user model if they're logged in.
5     def logged_in?
6       current_user != :false
7     end
8
9     # Accesses the current user from the session.  Set it to :false if login fails
10     # so that future calls do not hit the database.
11     def current_user
12       @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
13     end
14
15     # Store the given user in the session.
16     def current_user=(new_user)
17       session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
18       if new_user && !new_user.is_a?(Symbol)
19         set_varnish_auth_cookie
20       end
21       @current_user = new_user
22     end
23
24     def set_varnish_auth_cookie
25       cookies["_logged_in"] = {
26         :value => "true",
27         :domain => ".#{Gitorious.host}",
28         :expires => 3.weeks.from_now,
29         :httponly => true,
30         :secure => true
31       }
32     end
33
34     def clear_varnish_auth_cookie
35       cookies.delete "_logged_in", :domain => ".#{Gitorious.host}"
36     end
37
38     # Check if the user is authorized
39     #
40     # Override this method in your controllers if you want to restrict access
41     # to only a few actions or if you want to check if the user
42     # has the correct rights.
43     #
44     # Example:
45     #
46     #  # only allow nonbobs
47     #  def authorized?
48     #    current_user.login != "bob"
49     #  end
50     def authorized?
51       logged_in?
52     end
53
54     # Filter method to enforce a login requirement.
55     #
56     # To require logins for all actions, use this in your controllers:
57     #
58     #   before_filter :login_required
59     #
60     # To require logins for specific actions, use this in your controllers:
61     #
62     #   before_filter :login_required, :only => [ :edit, :update ]
63     #
64     # To skip this in a subclassed controller:
65     #
66     #   skip_before_filter :login_required
67     #
68     def login_required
69       authorized? || access_denied
70     end
71
72     # Redirect as appropriate when an access request fails.
73     #
74     # The default action is to redirect to the login screen.
75     #
76     # Override this method in your controllers if you want to have special
77     # behavior in case the user is not authorized
78     # to access the requested action.  For example, a popup window might
79     # simply close itself.
80     def access_denied
81       respond_to do |accepts|
82         accepts.html do
83           store_location
84           flash[:error] = "Action requires login"
85           if Gitorious.public?
86             redirect_to :controller => '/sessions', :action => 'new'
87           else
88             redirect_to root_path
89           end
90         end
91         accepts.xml do
92           headers["Status"]           = "Unauthorized"
93           headers["WWW-Authenticate"] = %(Basic realm="Web Password")
94           render :text => "Could't authenticate you", :status => '401 Unauthorized'
95         end
96       end
97       false
98     end
99
100     # Store the URI of the current request in the session.
101     #
102     # We can return to this location by calling #redirect_back_or_default.
103     def store_location(location = request.fullpath)
104       session[:return_to] = location
105     end
106
107     # Redirect to the URI stored by the most recent store_location call or
108     # to the passed default.
109     def redirect_back_or_default(default)
110       session[:return_to] ? redirect_to(session[:return_to]) : redirect_to(default)
111       session[:return_to] = nil
112     end
113
114     # Inclusion hook to make #current_user and #logged_in?
115     # available as ActionView helper methods.
116     def self.included(base)
117       if base.respond_to?(:helper_method)
118         base.send(:helper_method, :current_user, :logged_in?)
119       end
120     end
121
122     # Called from #current_user.  First attempt to login by the user id stored in the session.
123     def login_from_session
124       self.current_user = User.find_by_id(session[:user_id], :conditions => { :suspended_at => nil }) if session[:user_id]
125     end
126
127     # Called from #current_user.  Now, attempt to login by basic authentication information.
128     def login_from_basic_auth
129       username, passwd = get_auth_data
130       self.current_user = User.authenticate(username, passwd) if username && passwd
131     end
132
133     # Called from #current_user.  Finaly, attempt to login by an expiring token in the cookie.
134     def login_from_cookie
135       user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
136       if user && user.remember_token?
137         user.remember_me
138         cookies[:auth_token] = {
139           :value => user.remember_token,
140           :expires => user.remember_token_expires_at,
141           :secure => true
142         }
143         self.current_user = user
144       end
145     end
146
147   private
148     @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
149     # gets BASIC auth info
150     def get_auth_data
151       auth_key  = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
152       auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
153       return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
154     end
155 end