andreashappe / blubber

Simple authentication/acl system for rails

This URL has Read+Write access

Andreas Happe (author)
Wed Oct 22 13:59:12 -0700 2008
commit  c9edc0db23a49721d975232e596138186d901b3e
tree    b2716b95145b2414aa45d1f0338ed06e558e1d73
parent  8fab3d5e2527d68b0823f0fcf3f88c960e041fff
blubber / lib / blubber.rb
100644 248 lines (201 sloc) 6.293 kb
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
module Blubber
  module ControllerSupport
 
    @logger = Logger.new(STDERR)
    @@minimum_roles = nil
   
    # TODO: move to-be-inherited methods into an own module
 
    protected
 
    def current_user
      @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_user == false
      Thread.current[:user_id] = @current_user.id if @current_user
      @current_user
    end
 
    def acl_check
      user = current_user
      self.prepare(user, params)
 
# stat = add_stats(@current_user, params[:controller], params[:action], topic_instance)
 
      minimum_role = get_minimum_role(params[:action].to_sym)
 
      # if access was anonymous check the parameter for an email
      case minimum_role
      when :anonymous
        if params.include?(:email) && user.nil? then
          user = @current_user = User.find_by_email(params[:email])
        end
        return @current_user
      when :guest
        if params.include?(:email) && user.nil? then
          @current_user = User.find_by_email(params[:email])
          user = @current_user
        end
      end
 
      if user then
        if user.guest? then
          role = :guest
        else
          role = self.role_for(user)
        end
      else
        role = :anonymous
      end
 
      if permissions_sufficient(role, minimum_role) then
        current_user
      else
        access_denied
      end
    end
 
    def permissions_sufficient(role, minimum_role)
      case role
      when :moderator
        return true
      when :friend
        return true if minimum_role != :moderator
      when :user
        return true if [:anonymous, :guest, :user].include? minimum_role
      when :guest
        return true if [:anonymous, :guest].include? minimum_role
      when :anonymous
        return true if minimum_role == :anonymous
      end
 
      return false
    end
 
    def prepare_acl(acls, mode)
      result = {}
 
      acls.each do |action|
        result[action] = mode
      end
 
      return result
    end
 
    public
    def acl_map
      acls = {}
 
      acls.merge! prepare_acl(self.acl_friends, :friends)
      acls.merge! prepare_acl(self.acl_user, :user)
      acls.merge! prepare_acl(self.acl_guests, :guests)
      acls.merge! prepare_acl(self.acl_anonymous, :anonymous)
 
      acls
    end
 
    protected
    def self.generate_minimum_role_hash
      @logger.info "generated ACL table (not listed ACLs are :moderator)"
      @@minimum_roles = {}
      controllers = Dir.new("#{RAILS_ROOT}/app/controllers").entries
      controllers.each do |controller|
        if controller =~ /_controller/
          c = controller.camelize.gsub(".rb", "").constantize.new
          @@minimum_roles[c.controller_name.to_sym] = c.acl_map
          @logger.warn "#{c.controller_name} -> #{c.acl_map.inspect}"
        end
      end
    end
 
    def get_minimum_role(action)
      if RAILS_ENV == "production" && @@minimum_roles then
        minimum = @@minimum_roles[self.controller_name.to_sym][action]
        minimum ||= :moderator
      else
        minimum = :moderator
        minimum = :friend if self.acl_friends and self.acl_friends.include?(action)
        minimum = :user if self.acl_user and self.acl_user.include?(action)
        minimum = :guest if self.acl_guests and self.acl_guests.include?(action)
        minimum = :anonymous if self.acl_anonymous and self.acl_anonymous.include?(action)
      end
 
      return minimum
    end
 
    # Store the given user id in the session.
    def current_user=(new_user)
      Thread.current[:user_id] = session[:user_id] = new_user ? new_user.id : nil
    end
 
    # TODO add support for statistics..
    def add_stats(user, controller, action, topic_instance=nil)
      stat = StatsAccessLog.new
 
      stat.controller = StatsController.find_or_create_by_name(controller)
      stat.topic_instance = topic_instance
      stat.action = action
 
      stat.user_id = user.nil? ? 0 : user.id
      stat.save!
 
      stat
    end
 
    # Redirect as appropriate when an access request fails.
    #
    # The default action is to redirect to the login screen.
    #
    # Override this method in your controllers if you want to have special
    # behavior in case the user is not authorized
    # to access the requested action. For example, a popup window might
    # simply close itself.
    def access_denied
      respond_to do |format|
        format.html do
          store_location
          redirect_to new_session_path
        end
        format.any do
          request_http_basic_authentication 'Web Password'
        end
      end
    end
 
    #TODO investigate what is needed..
 
    def logged_in?
      current_user
    end
 
    private
 
    def store_location
      session[:return_to] = request.request_uri
    end
 
    def redirect_back_or_default(default)
      redirect_to(session[:return_to] || default)
      session[:return_to] = nil
    end
 
    def login_from_session
      self.current_user = User.find_by_id(session[:user_id]) if session[:user_id]
    end
 
    def login_from_basic_auth
      authenticate_with_http_basic do |username, password|
        self.current_user = User.authenticate(username, password)
      end
    end
 
    def login_from_cookie
      user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
      if user && user.remember_token?
        user.remember_me
        cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
        self.current_user = user
      end
    end
 
    protected
 
    def role_for(user)
      :user
    end
 
    def acl_friends
      []
    end
 
    def acl_user
      []
    end
 
    def acl_guests
      []
    end
 
    def acl_anonymous
      []
    end
 
    def prepare(user, params)
      nil
    end
  end
 
  module UserSupport
    # callback authenticate
 
    def current_user
      user_id = Thread.current[:user_id]
      raise "didn't set current user.." unless user_id
      return user_id
    end
 
    def current_user_object
      @current_user ||= User.find(current_user)
    end
 
    def current_user=(user)
      if user.is_a? User then
        Thread.current[:user_id] = user.id
      else
        Thread.current[:user_id] = user
      end
    end
  end
end