model_security_generator 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -69,13 +69,14 @@ public
69
69
  # If this method is called login_admin (it's an alias), keep trying
70
70
  # until an administrator logs in or the user pushes the "back" button.
71
71
  def login
72
- if User.current and (admin? or action_name != 'login_admin')
72
+ if flash[:login_succeeded]
73
73
  redirect_back_or_default :action => :success
74
74
  return
75
75
  end
76
76
 
77
77
  @user = User.new
78
78
 
79
+ flash[:login_succeeded] = false
79
80
  http_authorize
80
81
  end
81
82
 
@@ -93,7 +94,7 @@ public
93
94
  def logout
94
95
  User.sign_off
95
96
  reset_session
96
- session[:skip_user_setup] = true
97
+ flash[:skip_user_setup] = true
97
98
  redirect_to :action => 'login'
98
99
  end
99
100
 
@@ -112,8 +113,7 @@ public
112
113
  @user.admin = 1
113
114
  @user.activated = 1
114
115
  @user.save
115
- User.sign_on_by_session(1)
116
- session[:user_id] = 1
116
+ User.current = @user
117
117
  render :action => 'admin_created'
118
118
  # Mail the user instructions on how to activate their account.
119
119
  else
@@ -30,9 +30,14 @@ module Modal
30
30
  def modal_setup
31
31
  # Set modal return.
32
32
  if r = @params['ret']
33
+ logger.info("modal_setup: Save location #{r.sub(/\.L/, '#L')}")
33
34
  self.return_location = r.sub(/\.L/, '#L')
34
35
  elsif return_location.nil?
35
- self.return_location = @request.env['HTTP_REFERER']
36
+ r = @request.env['HTTP_REFERER']
37
+ if r
38
+ logger.info("modal_setup: Save HTTP Referer #{r}")
39
+ self.return_location = @request.env['HTTP_REFERER']
40
+ end
36
41
  end
37
42
  true
38
43
  end
@@ -40,19 +45,22 @@ module Modal
40
45
  # Get the location to return to after a modal action. The argument should
41
46
  # be a string containing a URL.
42
47
  def return_location
43
- @session[:return_to]
48
+ session[:return_to]
44
49
  end
45
50
 
46
51
  # Set the location to return to after a modal action. The return value should
47
52
  # be a string containing a URL.
48
53
  def return_location= a
49
- @session[:return_to] = a
54
+ logger.info("return_location= #{a}")
55
+ session[:return_to] = a
50
56
  end
51
57
 
52
58
  # Store the location to return to after a modal action from the request URI.
53
59
  # This is usually called before redirecting to another action.
54
60
  def store_location
55
- self.return_location = @request.request_uri
61
+ uri = @request.request_uri
62
+ logger.info("Store location: #{uri}")
63
+ self.return_location = uri
56
64
  end
57
65
 
58
66
  # Redirect to the stored return location. If no stored return location
@@ -61,11 +69,17 @@ module Modal
61
69
  def redirect_back_or_default(attributes = {}, *method_params)
62
70
  r = return_location
63
71
  if r
64
- redirect_to_url r
65
- self.return_location = nil
66
- else
67
- redirect_to attributes, method_params
72
+ if r == @request.request_uri
73
+ logger.info("redirect_back_or_default: BREAKING REDIRECTION LOOP #{r}.")
74
+ else
75
+ logger.info("redirect_back_or_default: return to #{r}")
76
+ self.return_location = nil
77
+ redirect_to_url r
78
+ return
79
+ end
68
80
  end
81
+ logger.info("redirect_back_or_default: go to default #{attributes.inspect}, #{method_params.inspect}")
82
+ redirect_to attributes, method_params
69
83
  end
70
84
 
71
85
  # Create a URL optionally including an internal anchor. If the +id+ argument
@@ -22,23 +22,28 @@ module UserSupport
22
22
  end
23
23
 
24
24
 
25
- # FIX: This only works for require_login and require_admin for now, because
26
- # I'm not passing the block across invocations.
27
- #
28
25
  # This is meant to be used as a before_filter.
29
26
  # A condition that is dependent on the user's login is in the block.
30
27
  # If the condition isn't true, a login panel is put up, and the explanation
31
28
  # that is passed as an argument may (or may not) be presented to the user,
32
29
  # depending on whether we're using HTTP authentication or not.
33
30
  # Once the condition is met, it resumes the action it was protecting.
34
- def require_condition(e)
31
+ def require_condition(header = nil, message = nil)
35
32
  if yield
36
33
  return true
37
34
  else
38
- if controller_name != 'user' and (action_name != 'login' and action_name != 'login_admin')
35
+ if controller_name != 'user' or (action_name != 'login' and action_name != 'login_admin')
39
36
  store_location
40
37
  end
41
- redirect_to :controller => 'user', :action => 'login', :explanation => e
38
+
39
+ # This test is to avoid writing the flash unnecessarily.
40
+ # Currently, writing the flash causes the entire session, not just the
41
+ # variables in question, to be written twice.
42
+ if header or message
43
+ flash[:login_header] = header
44
+ flash[:login_message] = message
45
+ end
46
+ redirect_to :controller => 'user', :action => 'login'
42
47
  return false
43
48
  end
44
49
  end
@@ -48,7 +53,11 @@ module UserSupport
48
53
  # isn't currently logged in. Once the administrator logs in, it resumes
49
54
  # the action it was protecting.
50
55
  def require_admin
51
- require_condition("Administrative user required.") { admin? }
56
+ header = "Administrative user required."
57
+ message = "You must be an administrative user to perform this action. " \
58
+ + "If you don't have an administrative login, please use the back button "\
59
+ + "of your browser to cancel this action."
60
+ require_condition(header, message) { admin? }
52
61
  end
53
62
 
54
63
  # This is meant to be used as a before_filter. It requires a
@@ -56,7 +65,7 @@ module UserSupport
56
65
  # logged in. Once a user logs in, it resumes the action it was
57
66
  # protecting.
58
67
  def require_login
59
- require_condition("Login required.") { User.current }
68
+ require_condition(nil, "You must be logged in to perform this action.") { User.current }
60
69
  end
61
70
 
62
71
  # This is a before filter for the entire application, used to set up the
@@ -72,23 +81,30 @@ module UserSupport
72
81
  # security tests of ModelSecurity that are based on User, or anything
73
82
  # that expects login information.
74
83
  #
84
+ # Keep this function in sync with User.current() and User.current=().
85
+ # It's aware of the way those functions store the user information.
86
+ #
75
87
  def user_setup
76
88
  # require_* use Modal to return to what they were doing after HTTP
77
89
  # authentication.
78
90
  modal_setup
79
91
 
92
+ # User.current=() needs a thread-global reference to the session.
93
+ Thread.current[:session] = session
94
+ logger.info("Session is #{Thread.current[:session]}")
95
+
80
96
  # This is used by the logout action to discard the old HTTP authentiction.
81
97
  # Logout redirects to login and that generates a new authentication
82
98
  # request. That request is the only input that can tell the browser to
83
99
  # stop sending the old authentication data with every request!
84
- if @session[:skip_user_setup] == true
85
- @session[:skip_user_setup] = false
100
+ if flash[:skip_user_setup] == true
101
+ flash[:skip_user_setup] = false
86
102
  return true
87
103
  end
88
104
 
89
- user = login = password = nil
90
-
105
+ login = password = nil
91
106
  r = @request.env
107
+ old_user = user = session[:user]
92
108
 
93
109
  # If the request contains an HTTP authentication, decode it.
94
110
  # Don't use it to authenticate the user yet.
@@ -102,11 +118,6 @@ module UserSupport
102
118
  end
103
119
  end
104
120
 
105
- # If the user is already logged into the session, get the user record.
106
- if (id = @session[:user_id])
107
- user = User.sign_on_by_session(id)
108
- end
109
-
110
121
  # If the HTTP authentication is for a different user name, the user wants
111
122
  # to change logins. This can happen if an operation requires an
112
123
  # administrative login and the current user isn't the administrator but
@@ -137,11 +148,13 @@ module UserSupport
137
148
  user = User.sign_on_by_token(@params[:id], @params['token'])
138
149
  end
139
150
 
140
- if user
141
- User.current = user
142
- @session[:user_id] = user.id
151
+ # User.current must always be set with each request. It's backed by a
152
+ # class-global variable.
153
+ User.current = user
154
+
155
+ if user != old_user
156
+ flash[:login_succeeded] = true
143
157
  end
144
- logger.info("Current user is #{User.current.inspect}.")
145
158
 
146
159
  true
147
160
  end
@@ -156,6 +156,53 @@ public
156
156
  let_write :password_confirmation, :if => :new_or_me?
157
157
  let_write :old_password, :if => :me?
158
158
 
159
+ # Return the user for the current request. It is guaranteed that this is
160
+ # set for each request in the before_filter for the application.
161
+ #
162
+ # This function uses the Ruby Thread class to do thread-local storage,
163
+ # which will be overkill if the Rails server implementation isn't also
164
+ # using Ruby threads, but works everywhere.
165
+ #
166
+ # User.current(), User.current=(), and UserSupport#user_setup encapsulate
167
+ # session storage of user information. Only these three functions should
168
+ # know whether we store the entire User object in the session or only
169
+ # User#id.
170
+ #
171
+ def User.current
172
+ # This does not refer to the session because the application has set
173
+ # this from the session in user_setup.
174
+ Thread.current[:user]
175
+ end
176
+
177
+ # Set the user for the current request. It is guaranteed that this is
178
+ # set for each request in the before_filter for the application.
179
+ #
180
+ # This function uses the Ruby Thread class to do thread-local storage,
181
+ # which will be overkill if the Rails server implementation isn't also
182
+ # using Ruby threads, but works everywhere.
183
+ #
184
+ # User.current(), User.current=(), and UserSupport#user_setup encapsulate
185
+ # session storage of user information. Only these three functions should
186
+ # know whether we store the entire User object in the session or only
187
+ # User#id.
188
+ #
189
+ def User.current=(u)
190
+ Thread.current[:user] = u
191
+
192
+ session = Thread.current[:session]
193
+
194
+ if session.nil?
195
+ message = "Programming error: Please add \"before_filter :user_setup\" to your application controller. See the ModelSecurity documentation."
196
+
197
+ raise RuntimeError.new(message)
198
+ end
199
+
200
+ # Don't cause a session store unnecessarily
201
+ if session[:user] != u
202
+ session[:user] = u
203
+ end
204
+ end
205
+
159
206
  # Change the user's password. Confirm the old password while doing so.
160
207
  def change_password(attributes)
161
208
  @password_is_new = true
@@ -172,26 +219,18 @@ public
172
219
  self.old_password = attributes['old_password']
173
220
  end
174
221
 
175
- # Return the currently-logged-in user.
176
- def User.current
177
- @current_user
178
- end
179
-
180
- # Set the currently-logged-in user.
181
- def User.current=(u)
182
- @current_user = u
183
- end
184
-
185
222
  # Return true if this record corresponds to the currently-logged-in user.
186
223
  # This is used as a security test.
187
224
  def me?
188
- User.current and User.current.id == id
225
+ u = User.current
226
+ u and u.id == id
189
227
  end
190
228
 
191
229
  # Return true if the currently-logged-in user is the administrator.
192
230
  # Class method. This is used as a pseudo-security test by let_display.
193
231
  def User.admin?
194
- return ((current != nil ) and (current.admin.to_i == 1))
232
+ u = User.current
233
+ return ((u != nil ) and (u.admin.to_i == 1))
195
234
  end
196
235
 
197
236
  # Return true if the currently-logged-in user is the administrator.
@@ -305,16 +344,6 @@ public
305
344
  end
306
345
  end
307
346
 
308
- # Continue the current login, from the session data.
309
- # This should be called by User.setup .
310
- def User.sign_on_by_session(user_id)
311
- begin
312
- return (User.current = User.find(user_id))
313
- rescue
314
- end
315
- return nil
316
- end
317
-
318
347
  # Sign on the user using a security token. Instance method.
319
348
  def sign_on_by_token(t)
320
349
  User.current = User.login_user
@@ -323,7 +352,7 @@ public
323
352
  self.token_expiry = Time.now
324
353
  self.activated = 1
325
354
  save
326
- User::current = self
355
+ User.current = self
327
356
  return self;
328
357
  end
329
358
  return nil
@@ -1,6 +1,16 @@
1
- <h1>Please login</h1>
1
+ <h1>
2
+ <% if flash[:login_header] %>
3
+ <%= flash[:login_header] + '. ' %>
4
+ <% end %>
5
+ Please log in.
6
+ </h1>
2
7
 
3
- <%= start_form_tag :action => 'login' %>
8
+ <% if flash[:login_message] %>
9
+ <p>
10
+ <%= flash[:login_message] %>
11
+ </p>
12
+ <% end %>
13
+ <%= form_tag :action => 'login' %>
4
14
  <p><label for="user_login">User ID</label><br/>
5
15
  <%= text_field 'user', 'login' %></p>
6
16
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.4
3
3
  specification_version: 1
4
4
  name: model_security_generator
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.6
7
- date: 2005-10-10
6
+ version: 0.0.7
7
+ date: 2005-10-11
8
8
  summary: "[Rails] Model security and authentication generator."
9
9
  require_paths:
10
10
  - "."