qrush-clearance 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/CHANGELOG.textile +176 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +123 -0
  4. data/Rakefile +103 -0
  5. data/TODO.textile +6 -0
  6. data/app/controllers/clearance/confirmations_controller.rb +72 -0
  7. data/app/controllers/clearance/passwords_controller.rb +80 -0
  8. data/app/controllers/clearance/sessions_controller.rb +66 -0
  9. data/app/controllers/clearance/users_controller.rb +34 -0
  10. data/app/models/clearance_mailer.rb +23 -0
  11. data/app/views/clearance_mailer/change_password.html.erb +7 -0
  12. data/app/views/clearance_mailer/confirmation.html.erb +2 -0
  13. data/app/views/passwords/edit.html.erb +23 -0
  14. data/app/views/passwords/new.html.erb +15 -0
  15. data/app/views/sessions/new.html.erb +28 -0
  16. data/app/views/users/_form.html.erb +13 -0
  17. data/app/views/users/new.html.erb +6 -0
  18. data/config/clearance_routes.rb +30 -0
  19. data/generators/clearance/USAGE +1 -0
  20. data/generators/clearance/clearance_generator.rb +41 -0
  21. data/generators/clearance/lib/insert_commands.rb +33 -0
  22. data/generators/clearance/lib/rake_commands.rb +22 -0
  23. data/generators/clearance/templates/README +22 -0
  24. data/generators/clearance/templates/factories.rb +13 -0
  25. data/generators/clearance/templates/migrations/create_users.rb +21 -0
  26. data/generators/clearance/templates/migrations/update_users.rb +41 -0
  27. data/generators/clearance/templates/user.rb +3 -0
  28. data/generators/clearance_features/USAGE +1 -0
  29. data/generators/clearance_features/clearance_features_generator.rb +20 -0
  30. data/generators/clearance_features/templates/features/password_reset.feature +33 -0
  31. data/generators/clearance_features/templates/features/sign_in.feature +42 -0
  32. data/generators/clearance_features/templates/features/sign_out.feature +23 -0
  33. data/generators/clearance_features/templates/features/sign_up.feature +45 -0
  34. data/generators/clearance_features/templates/features/step_definitions/clearance_steps.rb +110 -0
  35. data/generators/clearance_features/templates/features/step_definitions/factory_girl_steps.rb +5 -0
  36. data/generators/clearance_features/templates/features/support/paths.rb +22 -0
  37. data/generators/clearance_views/USAGE +0 -0
  38. data/generators/clearance_views/clearance_views_generator.rb +27 -0
  39. data/generators/clearance_views/templates/formtastic/passwords/edit.html.erb +21 -0
  40. data/generators/clearance_views/templates/formtastic/passwords/new.html.erb +15 -0
  41. data/generators/clearance_views/templates/formtastic/sessions/new.html.erb +22 -0
  42. data/generators/clearance_views/templates/formtastic/users/_inputs.html.erb +6 -0
  43. data/generators/clearance_views/templates/formtastic/users/new.html.erb +10 -0
  44. data/lib/clearance.rb +6 -0
  45. data/lib/clearance/authentication.rb +102 -0
  46. data/lib/clearance/extensions/errors.rb +6 -0
  47. data/lib/clearance/extensions/rescue.rb +3 -0
  48. data/lib/clearance/extensions/routes.rb +14 -0
  49. data/lib/clearance/user.rb +143 -0
  50. data/rails/init.rb +1 -0
  51. data/shoulda_macros/clearance.rb +268 -0
  52. metadata +145 -0
@@ -0,0 +1,21 @@
1
+ <h2>Change your password</h2>
2
+
3
+ <p>
4
+ Your password has been reset. Choose a new password below.
5
+ </p>
6
+
7
+ <% semantic_form_for(:user,
8
+ :url => user_password_path(@user, :token => @user.token),
9
+ :html => { :method => :put }) do |form| %>
10
+ <%= form.error_messages %>
11
+ <% form.inputs do -%>
12
+ <%= form.input :password, :as => :password,
13
+ :label => "Choose password" %>
14
+ <%= form.input :password_confirmation, :as => :password,
15
+ :label => "Confirm password" %>
16
+ <% end -%>
17
+ <% form.buttons do -%>
18
+ <%= form.commit_button "Save this password" %>
19
+ <% end -%>
20
+ <% end %>
21
+
@@ -0,0 +1,15 @@
1
+ <h2>Reset your password</h2>
2
+
3
+ <p>
4
+ We will email you a link to reset your password.
5
+ </p>
6
+
7
+ <% semantic_form_for :password, :url => passwords_path do |form| -%>
8
+ <% form.inputs do -%>
9
+ <%= form.input :email, :label => "Email address" %>
10
+ <% end -%>
11
+ <% form.buttons do -%>
12
+ <%= form.commit_button "Reset password" %>
13
+ <% end -%>
14
+ <% end -%>
15
+
@@ -0,0 +1,22 @@
1
+ <h2>Sign in</h2>
2
+
3
+ <% semantic_form_for :session, :url => session_path do |form| %>
4
+ <% form.inputs do %>
5
+ <%= form.input :email %>
6
+ <%= form.input :password, :as => :password %>
7
+ <%= form.input :remember_me, :as => :boolean, :required => false %>
8
+ <% end %>
9
+ <% form.buttons do %>
10
+ <%= form.commit_button "Sign in" %>
11
+ <% end %>
12
+ <% end %>
13
+
14
+ <ul>
15
+ <li>
16
+ <%= link_to "Sign up", new_user_path %>
17
+ </li>
18
+ <li>
19
+ <%= link_to "Forgot password?", new_password_path %>
20
+ </li>
21
+ </ul>
22
+
@@ -0,0 +1,6 @@
1
+ <% form.inputs do %>
2
+ <%= form.input :email %>
3
+ <%= form.input :password %>
4
+ <%= form.input :password_confirmation, :label => "Confirm password" %>
5
+ <% end %>
6
+
@@ -0,0 +1,10 @@
1
+ <h2>Sign up</h2>
2
+
3
+ <% semantic_form_for @user do |form| %>
4
+ <%= form.error_messages %>
5
+ <%= render :partial => "/users/inputs", :locals => { :form => form } %>
6
+ <% form.buttons do %>
7
+ <%= form.commit_button "Sign up" %>
8
+ <% end %>
9
+ <% end %>
10
+
data/lib/clearance.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'clearance/extensions/errors'
2
+ require 'clearance/extensions/rescue'
3
+ require 'clearance/extensions/routes'
4
+
5
+ require 'clearance/authentication'
6
+ require 'clearance/user'
@@ -0,0 +1,102 @@
1
+ module Clearance
2
+ module Authentication
3
+
4
+ def self.included(controller)
5
+ controller.send(:include, InstanceMethods)
6
+
7
+ controller.class_eval do
8
+ helper_method :current_user, :signed_in?, :signed_out?
9
+ hide_action :current_user, :signed_in?, :signed_out?
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ def current_user
15
+ @_current_user ||= (user_from_cookie || user_from_session)
16
+ end
17
+
18
+ def signed_in?
19
+ ! current_user.nil?
20
+ end
21
+
22
+ def signed_out?
23
+ current_user.nil?
24
+ end
25
+
26
+ protected
27
+
28
+ def authenticate
29
+ deny_access unless signed_in?
30
+ end
31
+
32
+ def user_from_session
33
+ if session[:user_id]
34
+ return nil unless user = ::User.find_by_id(session[:user_id])
35
+ return user if user.email_confirmed?
36
+ end
37
+ end
38
+
39
+ def user_from_cookie
40
+ if token = cookies[:remember_token]
41
+ return nil unless user = ::User.find_by_token(token)
42
+ return user if user.remember?
43
+ end
44
+ end
45
+
46
+ def sign_user_in(user)
47
+ warn "[DEPRECATION] sign_user_in: unnecessary. use sign_in(user) instead."
48
+ sign_in(user)
49
+ end
50
+
51
+ def sign_in(user)
52
+ if user
53
+ session[:user_id] = user.id
54
+ end
55
+ end
56
+
57
+ def remember?
58
+ params[:session] && params[:session][:remember_me] == "1"
59
+ end
60
+
61
+ def remember(user)
62
+ user.remember_me!
63
+ cookies[:remember_token] = { :value => user.token,
64
+ :expires => user.token_expires_at }
65
+ end
66
+
67
+ def forget(user)
68
+ user.forget_me! if user
69
+ cookies.delete(:remember_token)
70
+ reset_session
71
+ end
72
+
73
+ def redirect_back_or(default)
74
+ redirect_to(return_to || default)
75
+ clear_return_to
76
+ end
77
+
78
+ def return_to
79
+ session[:return_to] || params[:return_to]
80
+ end
81
+
82
+ def clear_return_to
83
+ session[:return_to] = nil
84
+ end
85
+
86
+ def redirect_to_root
87
+ redirect_to(root_url)
88
+ end
89
+
90
+ def store_location
91
+ session[:return_to] = request.request_uri if request.get?
92
+ end
93
+
94
+ def deny_access(flash_message = nil, opts = {})
95
+ store_location
96
+ flash[:failure] = flash_message if flash_message
97
+ redirect_to(new_session_url)
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,6 @@
1
+ if defined?(ActionController)
2
+ module ActionController
3
+ class Forbidden < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ if defined?(ActionController::Base)
2
+ ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
3
+ end
@@ -0,0 +1,14 @@
1
+ if defined?(ActionController::Routing::RouteSet)
2
+ class ActionController::Routing::RouteSet
3
+ def load_routes_with_clearance!
4
+ lib_path = File.dirname(__FILE__)
5
+ clearance_routes = File.join(lib_path, *%w[.. .. .. config clearance_routes.rb])
6
+ unless configuration_files.include?(clearance_routes)
7
+ add_configuration_file(clearance_routes)
8
+ end
9
+ load_routes_without_clearance!
10
+ end
11
+
12
+ alias_method_chain :load_routes!, :clearance
13
+ end
14
+ end
@@ -0,0 +1,143 @@
1
+ require 'digest/sha1'
2
+
3
+ module Clearance
4
+ module User
5
+
6
+ def self.included(model)
7
+ model.extend(ClassMethods)
8
+
9
+ model.send(:include, InstanceMethods)
10
+ model.send(:include, AttrAccessible)
11
+ model.send(:include, AttrAccessor)
12
+ model.send(:include, Validations)
13
+ model.send(:include, Callbacks)
14
+ end
15
+
16
+ module AttrAccessible
17
+ def self.included(model)
18
+ model.class_eval do
19
+ attr_accessible :email, :password, :password_confirmation
20
+ end
21
+ end
22
+ end
23
+
24
+ module AttrAccessor
25
+ def self.included(model)
26
+ model.class_eval do
27
+ attr_accessor :password, :password_confirmation
28
+ end
29
+ end
30
+ end
31
+
32
+ module Validations
33
+ def self.included(model)
34
+ model.class_eval do
35
+ validates_presence_of :email
36
+ validates_uniqueness_of :email, :case_sensitive => false
37
+ validates_format_of :email, :with => %r{.+@.+\..+}
38
+
39
+ validates_presence_of :password, :if => :password_required?
40
+ validates_confirmation_of :password, :if => :password_required?
41
+ end
42
+ end
43
+ end
44
+
45
+ module Callbacks
46
+ def self.included(model)
47
+ model.class_eval do
48
+ before_save :initialize_salt, :encrypt_password, :initialize_token
49
+ end
50
+ end
51
+ end
52
+
53
+ module InstanceMethods
54
+ def authenticated?(password)
55
+ encrypted_password == encrypt(password)
56
+ end
57
+
58
+ def encrypt(string)
59
+ generate_hash("--#{salt}--#{string}--")
60
+ end
61
+
62
+ def remember?
63
+ token_expires_at && Time.now.utc < token_expires_at
64
+ end
65
+
66
+ def remember_me!
67
+ remember_me_until! 2.weeks.from_now.utc
68
+ end
69
+
70
+ def forget_me!
71
+ clear_token
72
+ save(false)
73
+ end
74
+
75
+ def confirm_email!
76
+ self.email_confirmed = true
77
+ self.token = nil
78
+ save(false)
79
+ end
80
+
81
+ def forgot_password!
82
+ generate_token
83
+ save(false)
84
+ end
85
+
86
+ def update_password(new_password, new_password_confirmation)
87
+ self.password = new_password
88
+ self.password_confirmation = new_password_confirmation
89
+ clear_token if valid?
90
+ save
91
+ end
92
+
93
+ protected
94
+
95
+ def generate_hash(string)
96
+ Digest::SHA1.hexdigest(string)
97
+ end
98
+
99
+ def initialize_salt
100
+ if new_record?
101
+ self.salt = generate_hash("--#{Time.now.utc.to_s}--#{password}--")
102
+ end
103
+ end
104
+
105
+ def encrypt_password
106
+ return if password.blank?
107
+ self.encrypted_password = encrypt(password)
108
+ end
109
+
110
+ def generate_token
111
+ self.token = encrypt("--#{Time.now.utc.to_s}--#{password}--")
112
+ self.token_expires_at = nil
113
+ end
114
+
115
+ def clear_token
116
+ self.token = nil
117
+ self.token_expires_at = nil
118
+ end
119
+
120
+ def initialize_token
121
+ generate_token if new_record?
122
+ end
123
+
124
+ def password_required?
125
+ encrypted_password.blank? || !password.blank?
126
+ end
127
+
128
+ def remember_me_until!(time)
129
+ self.token_expires_at = time
130
+ self.token = encrypt("--#{token_expires_at}--#{password}--")
131
+ save(false)
132
+ end
133
+ end
134
+
135
+ module ClassMethods
136
+ def authenticate(email, password)
137
+ return nil unless user = find_by_email(email)
138
+ return user if user.authenticated?(password)
139
+ end
140
+ end
141
+
142
+ end
143
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'clearance'
@@ -0,0 +1,268 @@
1
+ module Clearance
2
+ module Shoulda
3
+
4
+ # STATE OF AUTHENTICATION
5
+
6
+ def should_be_signed_in_as(&block)
7
+ should "be signed in as #{block.bind(self).call}" do
8
+ user = block.bind(self).call
9
+ assert_not_nil user,
10
+ "please pass a User. try: should_be_signed_in_as { @user }"
11
+ assert_equal user, @controller.send(:current_user),
12
+ "#{user.inspect} is not the current_user, " <<
13
+ "which is #{@controller.send(:current_user).inspect}"
14
+ end
15
+ end
16
+
17
+ def should_be_signed_in_and_email_confirmed_as(&block)
18
+ warn "[DEPRECATION] should_be_signed_in_and_email_confirmed_as: questionable usefulness"
19
+ should_be_signed_in_as &block
20
+
21
+ should "have confirmed email" do
22
+ user = block.bind(self).call
23
+
24
+ assert_not_nil user
25
+ assert_equal user, assigns(:user)
26
+ assert assigns(:user).email_confirmed?
27
+ end
28
+ end
29
+
30
+ def should_not_be_signed_in
31
+ should "not be signed in" do
32
+ assert_nil session[:user_id]
33
+ end
34
+ end
35
+
36
+ def should_deny_access_on(http_method, action, opts = {})
37
+ warn "[DEPRECATION] should_deny_access_on: use a setup & should_deny_access(:flash => ?)"
38
+ flash_message = opts.delete(:flash)
39
+ context "on #{http_method} to #{action}" do
40
+ setup do
41
+ send(http_method, action, opts)
42
+ end
43
+
44
+ should_deny_access(:flash => flash_message)
45
+ end
46
+ end
47
+
48
+ def should_deny_access(opts = {})
49
+ if opts[:flash]
50
+ should_set_the_flash_to opts[:flash]
51
+ else
52
+ should_not_set_the_flash
53
+ end
54
+
55
+ should_redirect_to('new_session_url') { new_session_url }
56
+ end
57
+
58
+ # HTTP FLUENCY
59
+
60
+ def should_forbid(description, &block)
61
+ should "forbid #{description}" do
62
+ assert_raises ActionController::Forbidden do
63
+ instance_eval(&block)
64
+ end
65
+ end
66
+ end
67
+
68
+ # CONTEXTS
69
+
70
+ def signed_in_user_context(&blk)
71
+ warn "[DEPRECATION] signed_in_user_context: creates a Mystery Guest, causes Obscure Test"
72
+ context "A signed in user" do
73
+ setup do
74
+ @user = Factory(:user)
75
+ @user.confirm_email!
76
+ sign_in_as @user
77
+ end
78
+ merge_block(&blk)
79
+ end
80
+ end
81
+
82
+ def public_context(&blk)
83
+ warn "[DEPRECATION] public_context: common case is no-op. call sign_out otherwise"
84
+ context "The public" do
85
+ setup { sign_out }
86
+ merge_block(&blk)
87
+ end
88
+ end
89
+
90
+ # CREATING USERS
91
+
92
+ def should_create_user_successfully
93
+ warn "[DEPRECATION] should_create_user_successfully: not meant to be public, no longer used internally"
94
+ should_assign_to :user
95
+ should_change 'User.count', :by => 1
96
+
97
+ should "send the confirmation email" do
98
+ assert_sent_email do |email|
99
+ email.subject =~ /account confirmation/i
100
+ end
101
+ end
102
+
103
+ should_set_the_flash_to /confirm/i
104
+ should_redirect_to_url_after_create
105
+ end
106
+
107
+ # RENDERING
108
+
109
+ def should_render_nothing
110
+ should "render nothing" do
111
+ assert @response.body.blank?
112
+ end
113
+ end
114
+
115
+ # REDIRECTS
116
+
117
+ def should_redirect_to_url_after_create
118
+ should_redirect_to("the post-create url") do
119
+ @controller.send(:url_after_create)
120
+ end
121
+ end
122
+
123
+ def should_redirect_to_url_after_update
124
+ should_redirect_to("the post-update url") do
125
+ @controller.send(:url_after_update)
126
+ end
127
+ end
128
+
129
+ def should_redirect_to_url_after_destroy
130
+ should_redirect_to("the post-destroy url") do
131
+ @controller.send(:url_after_destroy)
132
+ end
133
+ end
134
+
135
+ def should_redirect_to_url_already_confirmed
136
+ should_redirect_to("the already confirmed url") do
137
+ @controller.send(:url_already_confirmed)
138
+ end
139
+ end
140
+
141
+ # VALIDATIONS
142
+
143
+ def should_validate_confirmation_of(attribute, opts = {})
144
+ warn "[DEPRECATION] should_validate_confirmation_of: not meant to be public, no longer used internally"
145
+ raise ArgumentError if opts[:factory].nil?
146
+
147
+ context "on save" do
148
+ should_validate_confirmation_is_not_blank opts[:factory], attribute
149
+ should_validate_confirmation_is_not_bad opts[:factory], attribute
150
+ end
151
+ end
152
+
153
+ def should_validate_confirmation_is_not_blank(factory, attribute, opts = {})
154
+ warn "[DEPRECATION] should_validate_confirmation_is_not_blank: not meant to be public, no longer used internally"
155
+ should "validate #{attribute}_confirmation is not blank" do
156
+ model = Factory.build(factory, blank_confirmation_options(attribute))
157
+ model.save
158
+ assert_confirmation_error(model, attribute,
159
+ "#{attribute}_confirmation cannot be blank")
160
+ end
161
+ end
162
+
163
+ def should_validate_confirmation_is_not_bad(factory, attribute, opts = {})
164
+ warn "[DEPRECATION] should_validate_confirmation_is_not_bad: not meant to be public, no longer used internally"
165
+ should "validate #{attribute}_confirmation is different than #{attribute}" do
166
+ model = Factory.build(factory, bad_confirmation_options(attribute))
167
+ model.save
168
+ assert_confirmation_error(model, attribute,
169
+ "#{attribute}_confirmation cannot be different than #{attribute}")
170
+ end
171
+ end
172
+
173
+ # FORMS
174
+
175
+ def should_display_a_password_update_form
176
+ warn "[DEPRECATION] should_display_a_password_update_form: not meant to be public, no longer used internally"
177
+ should "have a form for the user's token, password, and password confirm" do
178
+ update_path = ERB::Util.h(
179
+ user_password_path(@user, :token => @user.token)
180
+ )
181
+
182
+ assert_select 'form[action=?]', update_path do
183
+ assert_select 'input[name=_method][value=?]', 'put'
184
+ assert_select 'input[name=?]', 'user[password]'
185
+ assert_select 'input[name=?]', 'user[password_confirmation]'
186
+ end
187
+ end
188
+ end
189
+
190
+ def should_display_a_sign_up_form
191
+ warn "[DEPRECATION] should_display_a_sign_up_form: not meant to be public, no longer used internally"
192
+ should "display a form to sign up" do
193
+ assert_select "form[action=#{users_path}][method=post]",
194
+ true, "There must be a form to sign up" do
195
+ assert_select "input[type=text][name=?]",
196
+ "user[email]", true, "There must be an email field"
197
+ assert_select "input[type=password][name=?]",
198
+ "user[password]", true, "There must be a password field"
199
+ assert_select "input[type=password][name=?]",
200
+ "user[password_confirmation]", true, "There must be a password confirmation field"
201
+ assert_select "input[type=submit]", true,
202
+ "There must be a submit button"
203
+ end
204
+ end
205
+ end
206
+
207
+ def should_display_a_sign_in_form
208
+ warn "[DEPRECATION] should_display_a_sign_in_form: not meant to be public, no longer used internally"
209
+ should 'display a "sign in" form' do
210
+ assert_select "form[action=#{session_path}][method=post]",
211
+ true, "There must be a form to sign in" do
212
+ assert_select "input[type=text][name=?]",
213
+ "session[email]", true, "There must be an email field"
214
+ assert_select "input[type=password][name=?]",
215
+ "session[password]", true, "There must be a password field"
216
+ assert_select "input[type=checkbox][name=?]",
217
+ "session[remember_me]", true, "There must be a 'remember me' check box"
218
+ assert_select "input[type=submit]", true,
219
+ "There must be a submit button"
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ module Clearance
227
+ module Shoulda
228
+ module Helpers
229
+ def sign_in_as(user)
230
+ @controller.class_eval { attr_accessor :current_user }
231
+ @controller.current_user = user
232
+ return user
233
+ end
234
+
235
+ def sign_in
236
+ sign_in_as Factory(:email_confirmed_user)
237
+ end
238
+
239
+ def sign_out
240
+ @controller.class_eval { attr_accessor :current_user }
241
+ @controller.current_user = nil
242
+ end
243
+
244
+ def blank_confirmation_options(attribute)
245
+ warn "[DEPRECATION] blank_confirmation_options: not meant to be public, no longer used internally"
246
+ opts = { attribute => attribute.to_s }
247
+ opts.merge("#{attribute}_confirmation".to_sym => "")
248
+ end
249
+
250
+ def bad_confirmation_options(attribute)
251
+ warn "[DEPRECATION] bad_confirmation_options: not meant to be public, no longer used internally"
252
+ opts = { attribute => attribute.to_s }
253
+ opts.merge("#{attribute}_confirmation".to_sym => "not_#{attribute}")
254
+ end
255
+
256
+ def assert_confirmation_error(model, attribute, message = "confirmation error")
257
+ warn "[DEPRECATION] assert_confirmation_error: not meant to be public, no longer used internally"
258
+ assert model.errors.on(attribute).include?("doesn't match confirmation"),
259
+ message
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ class Test::Unit::TestCase
266
+ include Clearance::Shoulda::Helpers
267
+ end
268
+ Test::Unit::TestCase.extend(Clearance::Shoulda)