devise_invitable 1.1.8 → 1.3.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 (70) hide show
  1. data/CHANGELOG +5 -0
  2. data/README.rdoc +39 -12
  3. data/app/controllers/devise/invitations_controller.rb +20 -6
  4. data/app/controllers/devise_invitable/registrations_controller.rb +2 -2
  5. data/app/views/devise/mailer/invitation_instructions.html.erb +1 -1
  6. data/lib/devise_invitable/mailer.rb +3 -7
  7. data/lib/devise_invitable/model.rb +53 -59
  8. data/lib/devise_invitable/parameter_sanitizer.rb +11 -0
  9. data/lib/devise_invitable/rails.rb +1 -0
  10. data/lib/devise_invitable/version.rb +1 -1
  11. data/lib/devise_invitable.rb +1 -0
  12. data/lib/generators/active_record/templates/migration.rb +3 -2
  13. data/test/functional/controller_helpers_test.rb +39 -0
  14. data/test/functional/registrations_controller_test.rb +59 -0
  15. data/test/generators/views_generator_test.rb +40 -0
  16. data/test/generators_test.rb +34 -0
  17. data/test/integration/invitation_remove_test.rb +29 -0
  18. data/test/integration/invitation_test.rb +222 -0
  19. data/test/integration_tests_helper.rb +48 -0
  20. data/test/mailers/invitation_mail_test.rb +69 -0
  21. data/test/model_tests_helper.rb +33 -0
  22. data/test/models/invitable_test.rb +558 -0
  23. data/test/models_test.rb +74 -0
  24. data/test/orm/active_record.rb +4 -0
  25. data/test/orm/mongoid.rb +20 -0
  26. data/test/rails_app/Rakefile +7 -0
  27. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  28. data/test/rails_app/app/controllers/application_controller.rb +10 -0
  29. data/test/rails_app/app/controllers/free_invitations_controller.rb +6 -0
  30. data/test/rails_app/app/controllers/home_controller.rb +4 -0
  31. data/test/rails_app/app/controllers/users_controller.rb +12 -0
  32. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  33. data/test/rails_app/app/models/admin.rb +23 -0
  34. data/test/rails_app/app/models/octopussy.rb +15 -0
  35. data/test/rails_app/app/models/user.rb +56 -0
  36. data/test/rails_app/app/views/admins/new.html.erb +12 -0
  37. data/test/rails_app/app/views/free_invitations/new.html.erb +12 -0
  38. data/test/rails_app/app/views/home/index.html.erb +0 -0
  39. data/test/rails_app/app/views/layouts/application.html.erb +16 -0
  40. data/test/rails_app/app/views/users/invitations/new.html.erb +15 -0
  41. data/test/rails_app/config/application.rb +24 -0
  42. data/test/rails_app/config/boot.rb +11 -0
  43. data/test/rails_app/config/database.yml +22 -0
  44. data/test/rails_app/config/environment.rb +5 -0
  45. data/test/rails_app/config/environments/development.rb +25 -0
  46. data/test/rails_app/config/environments/production.rb +49 -0
  47. data/test/rails_app/config/environments/test.rb +33 -0
  48. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  49. data/test/rails_app/config/initializers/devise.rb +213 -0
  50. data/test/rails_app/config/initializers/inflections.rb +10 -0
  51. data/test/rails_app/config/initializers/mime_types.rb +5 -0
  52. data/test/rails_app/config/initializers/secret_token.rb +7 -0
  53. data/test/rails_app/config/initializers/session_store.rb +8 -0
  54. data/test/rails_app/config/initializers/wrap_parameters.rb +14 -0
  55. data/test/rails_app/config/locales/devise.en.yml +57 -0
  56. data/test/rails_app/config/locales/en.yml +14 -0
  57. data/test/rails_app/config/routes.rb +9 -0
  58. data/test/rails_app/config.ru +4 -0
  59. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +39 -0
  60. data/test/rails_app/mongoid.yml +10 -0
  61. data/test/rails_app/script/rails +6 -0
  62. data/test/routes_test.rb +20 -0
  63. data/test/test_helper.rb +24 -0
  64. metadata +135 -47
  65. data/app/controllers/devise_invitable/registrations_controller.rb~ +0 -15
  66. data/lib/devise_invitable/controllers/helpers.rb~ +0 -21
  67. data/lib/devise_invitable/controllers/registrations.rb~ +0 -21
  68. data/lib/devise_invitable/model.rb~ +0 -224
  69. data/lib/devise_invitable/rails.rb~ +0 -21
  70. data/lib/devise_invitable.rb~ +0 -65
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ = 1.3.0
2
+ Now devise 3.1 compatible, @token must be used instead of @resource.invitation_token in mail views
3
+
4
+ = 1.2.0
5
+ Add invitation_created_at column which is set when invitation is created even when sending is skipped. This new field is used to check invitation period valid
data/README.rdoc CHANGED
@@ -3,7 +3,9 @@
3
3
 
4
4
  It adds support to devise[http://github.com/plataformatec/devise] for send invitations by email (it requires to be authenticated) and accept the invitation setting the password.
5
5
 
6
- DeviseInvitable currently only support Rails 3, if you want to use it with Rails 2.3 you must install version {0.2.3}[http://rubygems.org/gems/devise_invitable/versions/0.2.3]
6
+ DeviseInvitable currently supports Rails 3 and 4, if you want to use it with Rails 2.3 you must install version {0.2.3}[http://rubygems.org/gems/devise_invitable/versions/0.2.3]
7
+
8
+ If you want to use devise 3.0.x, you must use 1.2.1, newer versions require devise >= 3.1.0
7
9
 
8
10
  == Installation
9
11
 
@@ -45,7 +47,8 @@ Add t.invitable to your Devise model migration:
45
47
  create_table :users do
46
48
  ...
47
49
  ## Invitable
48
- t.string :invitation_token, :limit => 60
50
+ t.string :invitation_token
51
+ t.datetime :invitation_created_at
49
52
  t.datetime :invitation_sent_at
50
53
  t.datetime :invitation_accepted_at
51
54
  t.integer :invitation_limit
@@ -57,14 +60,15 @@ Add t.invitable to your Devise model migration:
57
60
 
58
61
  or for a model that already exists, define a migration to add DeviseInvitable to your model:
59
62
 
60
- change_table :users do |t|
61
- t.string :invitation_token, :limit => 60
62
- t.datetime :invitation_sent_at
63
- t.datetime :invitation_accepted_at
64
- t.integer :invitation_limit
65
- t.integer :invited_by_id
66
- t.string :invited_by_type
67
-
63
+ def change
64
+ add_column :users, :invitation_token, :string
65
+ add_column :users, :invitation_created_at, :datetime
66
+ add_column :users, :invitation_sent_at, :datetime
67
+ add_column :users, :invitation_accepted_at, :datetime
68
+ add_column :users, :invitation_limit, :integer
69
+ add_column :users, :invited_by_id, :integer
70
+ add_column :users, :invited_by_type, :string
71
+ add_index :users, :invitation_token, :unique => true
68
72
  end
69
73
 
70
74
  # Allow null encrypted_password
@@ -72,10 +76,21 @@ or for a model that already exists, define a migration to add DeviseInvitable to
72
76
  # Allow null password_salt (add it if you are using Devise's encryptable module)
73
77
  change_column :users, :password_salt, :string, :null => true
74
78
 
79
+ If you previously used devise_invitable with a :limit on :invitation_token, remove it:
80
+
81
+ def up
82
+ change_column :users, :invitation_token, :string, :limit => nil
83
+ end
84
+
85
+ def down
86
+ change_column :users, :invitation_token, :string, :limit => 60
87
+ end
88
+
75
89
  == Mongoid Field Definitions
76
90
  If you are using Mongoid, define the following fields and indexes within your invitable model:
77
91
 
78
92
  field :invitation_token, type: String
93
+ field :invitation_created_at, type: Time
79
94
  field :invitation_sent_at, type: Time
80
95
  field :invitation_accepted_at, type: Time
81
96
  field :invitation_limit, type: Integer
@@ -129,6 +144,10 @@ You can also use the generator to generate scoped views:
129
144
 
130
145
  rails generate devise_invitable:views users
131
146
 
147
+ Then turn scoped views on in config/initializers/devise.rb:
148
+
149
+ config.scoped_views = true
150
+
132
151
  Please refer to {Devise's README}[http://github.com/plataformatec/devise] for more information about views.
133
152
 
134
153
  == Configuring controllers
@@ -153,6 +172,15 @@ be sure that you generate the views and put them into the controller that you ge
153
172
 
154
173
  rails generate devise_invitable:views users/invitations
155
174
 
175
+ == Strong Parameters
176
+
177
+ When you customize your own views, you may end up adding new attributes to forms. Rails 4 moved the parameter sanitization from the model to the controller, causing DeviseInvitable to handle this concern at the controller as well. Read about it in {devise README}[http://github.com/plataformatec/devise#strong-parameters]
178
+
179
+ There are just two actions in DeviseInvitable that allows any set of parameters to be passed down to the model, therefore requiring sanitization. Their names and the permited parameters by default are:
180
+
181
+ * invite (Devise::InvitationsController#create) - Permits only the authentication keys (like email)
182
+ * accept_invitation (Devise::InvitationsController#update) - Permits invitation_token plus password and password_confirmation
183
+
156
184
  == Usage
157
185
 
158
186
  === Send an invitation
@@ -173,8 +201,7 @@ When skip_invitation is used, you must also then set the invitation_sent_at fiel
173
201
  token. Failure to do so will yield "Invalid invitation token" errors when the user attempts to accept the invite.
174
202
  You can set it like so:
175
203
 
176
- user.invitation_sent_at = Time.now.utc
177
- user.save!
204
+ user.deliver_invitation
178
205
 
179
206
  You can add :skip_invitation to attributes hash if skip_invitation is added to attr_accessible.
180
207
 
@@ -8,16 +8,16 @@ class Devise::InvitationsController < DeviseController
8
8
 
9
9
  # GET /resource/invitation/new
10
10
  def new
11
- build_resource
11
+ self.resource = resource_class.new
12
12
  render :new
13
13
  end
14
14
 
15
15
  # POST /resource/invitation
16
16
  def create
17
- self.resource = resource_class.invite!(resource_params, current_inviter)
17
+ self.resource = invite_resource
18
18
 
19
19
  if resource.errors.empty?
20
- set_flash_message :notice, :send_instructions, :email => self.resource.email
20
+ set_flash_message :notice, :send_instructions, :email => self.resource.email if self.resource.invitation_sent_at
21
21
  respond_with resource, :location => after_invite_path_for(resource)
22
22
  else
23
23
  respond_with_navigational(resource) { render :new }
@@ -26,12 +26,13 @@ class Devise::InvitationsController < DeviseController
26
26
 
27
27
  # GET /resource/invitation/accept?invitation_token=abcdef
28
28
  def edit
29
+ resource.invitation_token = params[:invitation_token]
29
30
  render :edit
30
31
  end
31
32
 
32
33
  # PUT /resource/invitation
33
34
  def update
34
- self.resource = resource_class.accept_invitation!(resource_params)
35
+ self.resource = resource_class.accept_invitation!(update_resource_params)
35
36
 
36
37
  if resource.errors.empty?
37
38
  flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
@@ -51,24 +52,37 @@ class Devise::InvitationsController < DeviseController
51
52
  end
52
53
 
53
54
  protected
55
+
56
+ def invite_resource
57
+ resource_class.invite!(invite_params, current_inviter)
58
+ end
59
+
54
60
  def current_inviter
55
61
  @current_inviter ||= authenticate_inviter!
56
62
  end
57
63
 
58
64
  def has_invitations_left?
59
65
  unless current_inviter.nil? || current_inviter.has_invitations_left?
60
- build_resource
66
+ self.resource = resource_class.new
61
67
  set_flash_message :alert, :no_invitations_remaining
62
68
  respond_with_navigational(resource) { render :new }
63
69
  end
64
70
  end
65
71
 
66
72
  def resource_from_invitation_token
67
- unless params[:invitation_token] && self.resource = resource_class.to_adapter.find_first(params.slice(:invitation_token))
73
+ unless params[:invitation_token] && self.resource = resource_class.find_by_invitation_token(params[:invitation_token], true)
68
74
  set_flash_message(:alert, :invitation_token_invalid)
69
75
  redirect_to after_sign_out_path_for(resource_name)
70
76
  end
71
77
  end
78
+
79
+ def invite_params
80
+ devise_parameter_sanitizer.sanitize(:invite)
81
+ end
82
+
83
+ def update_resource_params
84
+ devise_parameter_sanitizer.sanitize(:accept_invitation)
85
+ end
72
86
 
73
87
  end
74
88
 
@@ -1,8 +1,8 @@
1
1
  class DeviseInvitable::RegistrationsController < Devise::RegistrationsController
2
2
  protected
3
3
 
4
- def build_resource(*args)
5
- hash = args.pop || resource_params || {}
4
+ def build_resource(hash = nil)
5
+ hash ||= resource_params || {}
6
6
  if hash[:email]
7
7
  self.resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
8
8
  if self.resource
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>Someone has invited you to <%= root_url %>, you can accept it through the link below.</p>
4
4
 
5
- <p><%= link_to 'Accept invitation', accept_invitation_url(@resource, :invitation_token => @resource.invitation_token) %></p>
5
+ <p><%= link_to 'Accept invitation', accept_invitation_url(@resource, :invitation_token => @token) %></p>
6
6
 
7
7
  <p>If you don't want to accept the invitation, please ignore this email.<br />
8
8
  Your account won't be created until you access the link above and set your password.</p>
@@ -4,13 +4,9 @@ module DeviseInvitable
4
4
  module Mailer
5
5
 
6
6
  # Deliver an invitation email
7
- def invitation_instructions(record, opts={})
8
- # optional arguments introduced in Devise 2.2.0, remove check once support for < 2.2.0 is dropped.
9
- if Gem::Version.new(Devise::VERSION.dup) < Gem::Version.new('2.2.0')
10
- devise_mail(record, :invitation_instructions)
11
- else
12
- devise_mail(record, :invitation_instructions, opts)
13
- end
7
+ def invitation_instructions(record, token, opts={})
8
+ @token = token
9
+ devise_mail(record, :invitation_instructions, opts)
14
10
  end
15
11
  end
16
12
  end
@@ -42,7 +42,7 @@ module Devise
42
42
  attr_writer :skip_password
43
43
 
44
44
  scope :active, lambda { where(:invitation_token => nil) }
45
- if defined?(Mongoid) && self < Mongoid::Document
45
+ if defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
46
46
  scope :invitation_not_accepted, lambda { where(:invitation_accepted_at => nil, :invitation_token.ne => nil) }
47
47
  scope :invitation_accepted, lambda { where(:invitation_accepted_at.ne => nil) }
48
48
  else
@@ -50,15 +50,13 @@ module Devise
50
50
  scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
51
51
 
52
52
  [:before_invitation_accepted, :after_invitation_accepted].each do |callback_method|
53
- send callback_method do
54
- notify_observers callback_method
55
- end
53
+ send callback_method
56
54
  end
57
55
  end
58
56
  end
59
57
 
60
58
  def self.required_fields(klass)
61
- fields = [:invitation_token, :invitation_sent_at, :invitation_accepted_at,
59
+ fields = [:invitation_token, :invitation_created_at, :invitation_sent_at, :invitation_accepted_at,
62
60
  :invitation_limit, :invited_by_id, :invited_by_type]
63
61
  if Devise.invited_by_class_name
64
62
  fields -= [:invited_by_type]
@@ -66,14 +64,6 @@ module Devise
66
64
  fields
67
65
  end
68
66
 
69
- def invitation_fields
70
- fields = [:invitation_sent_at, :invited_by_id, :invited_by_type]
71
- if Devise.invited_by_class_name
72
- fields -= [:invited_by_type]
73
- end
74
- fields
75
- end
76
-
77
67
  # Accept an invitation by clearing invitation token and and setting invitation_accepted_at
78
68
  def accept_invitation
79
69
  self.invitation_accepted_at = Time.now.utc
@@ -113,11 +103,6 @@ module Devise
113
103
  invitation_accepted? || !invited_to_sign_up?
114
104
  end
115
105
 
116
- def invited?
117
- ActiveSupport::Deprecation.warn "invited? is deprecated and will be removed from DeviseInvitable 1.1.0 (use invited_to_sign_up? instead)"
118
- invited_to_sign_up?
119
- end
120
-
121
106
  # Reset invitation token and send invitation again
122
107
  def invite!(invited_by = nil)
123
108
  was_invited = invited_to_sign_up?
@@ -128,8 +113,9 @@ module Devise
128
113
  def self.confirmation_required?; false; end
129
114
  end
130
115
 
131
- generate_invitation_token if self.invitation_token.nil?
132
- self.invitation_sent_at = Time.now.utc unless @skip_invitation
116
+ generate_invitation_token if self.invitation_token.nil? || (!@skip_invitation && @raw_invitation_token.nil?)
117
+ self.invitation_created_at = Time.now.utc
118
+ self.invitation_sent_at = self.invitation_created_at unless @skip_invitation
133
119
  self.invited_by = invited_by if invited_by
134
120
 
135
121
  # Call these before_validate methods since we aren't validating on save
@@ -159,13 +145,19 @@ module Devise
159
145
  accept_invitation! if invited_to_sign_up?
160
146
  end
161
147
 
162
- def invite_key_valid?
163
- return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
164
- self.class.invite_key.all? do |key, regexp|
165
- regexp.nil? || self.send(key).try(:match, regexp)
148
+ def clear_errors_on_valid_keys
149
+ self.class.invite_key.each do |key, regexp|
150
+ self.errors.delete(key) if regexp.nil? || self.send(key).try(:match, regexp)
166
151
  end
167
152
  end
168
153
 
154
+ # Deliver the invitation email
155
+ def deliver_invitation
156
+ generate_invitation_token! unless @raw_invitation_token
157
+ self.update_attribute :invitation_sent_at, Time.now.utc unless self.invitation_sent_at
158
+ send_devise_notification(:invitation_instructions, @raw_invitation_token)
159
+ end
160
+
169
161
  protected
170
162
  # Overriding the method in Devise's :validatable module so password is not required on inviting
171
163
  def password_required?
@@ -176,11 +168,6 @@ module Devise
176
168
  respond_to?(:confirmation_required?, true) && confirmation_required? && invitation_accepted?
177
169
  end
178
170
 
179
- # Deliver the invitation email
180
- def deliver_invitation
181
- send_devise_notification(:invitation_instructions)
182
- end
183
-
184
171
  # Checks if the invitation for the user is within the limit time.
185
172
  # We do this by calculating if the difference between today and the
186
173
  # invitation sent date does not exceed the invite for time configured.
@@ -201,32 +188,35 @@ module Devise
201
188
  # invitation_period_valid? # will always return true
202
189
  #
203
190
  def invitation_period_valid?
204
- invitation_sent_at && (self.class.invite_for.to_i.zero? || invitation_sent_at.utc >= self.class.invite_for.ago)
191
+ time = invitation_created_at || invitation_sent_at
192
+ time && (self.class.invite_for.to_i.zero? || time.utc >= self.class.invite_for.ago)
205
193
  end
206
194
 
207
195
  # Generates a new random token for invitation, and stores the time
208
196
  # this token is being generated
209
197
  def generate_invitation_token
210
- self.invitation_token = self.class.invitation_token
198
+ raw, enc = Devise.token_generator.generate(self.class, :invitation_token)
199
+ @raw_invitation_token = raw
200
+ self.invitation_token = enc
201
+ end
202
+
203
+ def generate_invitation_token!
204
+ generate_invitation_token && save(:validate => false)
211
205
  end
212
206
 
213
207
  module ClassMethods
214
208
  # Return fields to invite
215
209
  def invite_key_fields
216
- if invite_key.is_a? Hash
217
- invite_key.keys
218
- else
219
- ActiveSupport::Deprecation.warn("invite_key should be a hash like {#{invite_key.inspect} => /.../}")
220
- Array(invite_key)
221
- end
210
+ invite_key.keys
222
211
  end
223
212
 
224
- # Attempt to find a user by it's email. If a record is not found, create a new
225
- # user and send invitation to it. If user is found, returns the user with an
226
- # email already exists error.
227
- # If user is found and still have pending invitation, email is resend unless
228
- # resend_invitation is set to false
229
- # Attributes must contain the user email, other attributes will be set in the record
213
+ # Attempt to find a user by its email. If a record is not found,
214
+ # create a new user and send an invitation to it. If the user is found,
215
+ # return the user with an email already exists error.
216
+ # If the user is found and still has a pending invitation, invitation
217
+ # email is resent unless resend_invitation is set to false.
218
+ # Attributes must contain the user's email, other attributes will be
219
+ # set in the record
230
220
  def _invite(attributes={}, invited_by=nil, &block)
231
221
  invite_key_array = invite_key_fields
232
222
  attributes_hash = {}
@@ -235,34 +225,26 @@ module Devise
235
225
  end
236
226
 
237
227
  invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
238
- invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
228
+ invitable.assign_attributes(attributes)
239
229
  invitable.invited_by = invited_by
240
230
 
241
231
  invitable.skip_password = true
242
232
  invitable.valid? if self.validate_on_invite
243
233
  if invitable.new_record?
244
- invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
234
+ invitable.clear_errors_on_valid_keys if !self.validate_on_invite
245
235
  elsif !invitable.invited_to_sign_up? || !self.resend_invitation
246
236
  invite_key_array.each do |key|
247
237
  invitable.errors.add(key, :taken)
248
238
  end
249
239
  end
250
240
 
251
- if invitable.errors.empty?
252
- yield invitable if block_given?
253
- mail = invitable.invite!
254
- end
241
+ yield invitable if block_given?
242
+ mail = invitable.invite! if invitable.errors.empty?
255
243
  [invitable, mail]
256
244
  end
257
245
 
258
- # Override this method if the invitable is using Mass Assignment Security
259
- # and the inviter has a non-default role.
260
- def inviter_role(inviter)
261
- :default
262
- end
263
-
264
246
  def invite!(attributes={}, invited_by=nil, &block)
265
- invitable, mail = _invite(attributes, invited_by, &block)
247
+ invitable, mail = _invite(attributes.with_indifferent_access, invited_by, &block)
266
248
  invitable
267
249
  end
268
250
 
@@ -277,15 +259,27 @@ module Devise
277
259
  # error in invitation_token attribute.
278
260
  # Attributes must contain invitation_token, password and confirmation
279
261
  def accept_invitation!(attributes={})
280
- invitable = find_or_initialize_with_error_by(:invitation_token, attributes.delete(:invitation_token))
281
- invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
262
+ original_token = attributes.delete(:invitation_token)
263
+ invitable = find_by_invitation_token(original_token, false)
282
264
  if invitable.errors.empty?
283
- invitable.assign_attributes(attributes, :as => inviter_role(self))
265
+ invitable.assign_attributes(attributes)
284
266
  invitable.accept_invitation!
285
267
  end
286
268
  invitable
287
269
  end
288
270
 
271
+ def find_by_invitation_token(original_token, only_valid)
272
+ invitation_token = Devise.token_generator.digest(self, :invitation_token, original_token)
273
+
274
+ invitable = find_or_initialize_with_error_by(:invitation_token, invitation_token)
275
+ if !invitable.persisted? && Devise.allow_insecure_token_lookup
276
+ invitable = find_or_initialize_with_error_by(:invitation_token, original_token)
277
+ end
278
+ invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
279
+ invitable.invitation_token = original_token
280
+ invitable unless only_valid && invitable.errors.present?
281
+ end
282
+
289
283
  # Generate a token checking if one does not already exist in the database.
290
284
  def invitation_token
291
285
  generate_token(:invitation_token)
@@ -0,0 +1,11 @@
1
+ module DeviseInvitable
2
+ module ParameterSanitizer
3
+ def invite
4
+ default_params.permit(resource_class.invite_key_fields)
5
+ end
6
+
7
+ def accept_invitation
8
+ default_params.permit([:password, :password_confirmation, :invitation_token])
9
+ end
10
+ end
11
+ end
@@ -17,6 +17,7 @@ module DeviseInvitable
17
17
  # extend mapping with after_initialize becuase is not reloaded
18
18
  config.after_initialize do
19
19
  Devise::Mapping.send :include, DeviseInvitable::Mapping
20
+ Devise::ParameterSanitizer.send :include, DeviseInvitable::ParameterSanitizer
20
21
  end
21
22
  end
22
23
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseInvitable
2
- VERSION = '1.1.8'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -2,6 +2,7 @@ module DeviseInvitable
2
2
  autoload :Inviter, 'devise_invitable/inviter'
3
3
  autoload :Mailer, 'devise_invitable/mailer'
4
4
  autoload :Mapping, 'devise_invitable/mapping'
5
+ autoload :ParameterSanitizer, 'devise_invitable/parameter_sanitizer'
5
6
  module Controllers
6
7
  autoload :UrlHelpers, 'devise_invitable/controllers/url_helpers'
7
8
  autoload :Registrations, 'devise_invitable/controllers/registrations'
@@ -1,7 +1,8 @@
1
1
  class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
2
2
  def up
3
3
  change_table :<%= table_name %> do |t|
4
- t.string :invitation_token, :limit => 60
4
+ t.string :invitation_token
5
+ t.datetime :invitation_created_at
5
6
  t.datetime :invitation_sent_at
6
7
  t.datetime :invitation_accepted_at
7
8
  t.integer :invitation_limit
@@ -20,7 +21,7 @@ class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
20
21
  def down
21
22
  change_table :<%= table_name %> do |t|
22
23
  t.remove_references :invited_by, :polymorphic => true
23
- t.remove :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token
24
+ t.remove :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
24
25
  end
25
26
  end
26
27
  end
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+
3
+ class ControllerHelpersTest < ActionController::TestCase
4
+ tests ApplicationController
5
+
6
+ test "after invite path defaults to after sign in path" do
7
+ assert_equal @controller.after_sign_in_path_for(:user), @controller.after_invite_path_for(:user)
8
+ end
9
+
10
+ test "after accept path defaults to after sign in path" do
11
+ assert_equal @controller.after_sign_in_path_for(:user), @controller.after_accept_path_for(:user)
12
+ end
13
+
14
+ test 'after invite path is customizable from application controller' do
15
+ custom_path = 'customized/after/invite/path'
16
+ @controller.instance_eval "def after_invite_path_for(resource) '#{custom_path}' end"
17
+ assert_equal @controller.after_invite_path_for(:user), custom_path
18
+ end
19
+
20
+ test 'after accept path is customizable from application controller' do
21
+ custom_path = 'customized/after/accept/path'
22
+ @controller.instance_eval "def after_accept_path_for(resource) '#{custom_path}' end"
23
+ assert_equal @controller.after_accept_path_for(:user), custom_path
24
+ end
25
+
26
+ test 'is not a devise controller' do
27
+ assert !@controller.devise_controller?
28
+ end
29
+
30
+ test 'invitations controller respects definition for after invite path in application controller' do
31
+ assert Devise::InvitationsController.method_defined? :after_invite_path_for
32
+ assert !Devise::InvitationsController.instance_methods(false).include?(:after_invite_path_for)
33
+ end
34
+
35
+ test 'invitations controller respects definition for after accept path in application controller' do
36
+ assert Devise::InvitationsController.method_defined? :after_accept_path_for
37
+ assert !Devise::InvitationsController.instance_methods(false).include?(:after_accept_path_for)
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+ require 'model_tests_helper'
3
+
4
+ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
5
+ def setup
6
+ @issuer = new_user#users(:issuer)
7
+ @issuer.valid?
8
+ assert @issuer.valid?, 'starting with a valid user record'
9
+
10
+ # josevalim: you are required to do that because the routes sets this kind
11
+ # of stuff automatically. But functional tests are not using the routes.
12
+ # see https://github.com/plataformatec/devise/issues/1196
13
+ @request.env["devise.mapping"] = Devise.mappings[:user]
14
+ end
15
+
16
+ test "invited users may still sign up directly by themselves" do
17
+ # invite the invitee
18
+ sign_in @issuer
19
+ invitee_email = "invitee@example.org"
20
+
21
+ User.invite!(:email => invitee_email) do |u|
22
+ u.skip_invitation = true
23
+ u.invited_by = @issuer
24
+ end
25
+ sign_out @issuer
26
+
27
+ @invitee = User.where(:email => invitee_email).first
28
+ assert_blank @invitee.encrypted_password, "the password should be unset"
29
+
30
+ # sign_up the invitee
31
+ assert_difference('ActionMailer::Base.deliveries.size') do
32
+ post :create, :user => {:email => invitee_email, :password => "1password", :bio => '.'}
33
+ end
34
+
35
+ @invitee = User.where(:email => invitee_email).first
36
+ assert_present @invitee.encrypted_password
37
+ assert_not_nil @invitee.invitation_accepted_at
38
+ assert_nil @invitee.invitation_token
39
+ assert_present @invitee.invited_by_id
40
+ assert_present @invitee.invited_by_type
41
+ assert !@invitee.confirmed?
42
+ assert_present @invitee.confirmation_token
43
+ end
44
+
45
+ test "not invitable resources can register" do
46
+ @request.env["devise.mapping"] = Devise.mappings[:admin]
47
+ invitee_email = "invitee@example.org"
48
+
49
+ post :create, :admin => {:email => invitee_email, :password => "1password"}
50
+
51
+ @invitee = Admin.where(:email => invitee_email).first
52
+ assert_present @invitee.encrypted_password
53
+ end
54
+
55
+ test "missing params on a create should not cause an error" do
56
+
57
+ assert_nothing_raised { post :create }
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+ require 'fileutils'
3
+ require 'rails/generators'
4
+ require File.expand_path('../../lib/generators/devise_invitable/views_generator.rb', File.dirname(__FILE__))
5
+
6
+ class ViewsGeneratorTest < ::Rails::Generators::TestCase
7
+ tests DeviseInvitable::Generators::ViewsGenerator
8
+ destination File.expand_path('../../tmp', File.dirname(__FILE__))
9
+
10
+ test 'views get copied' do
11
+ run_generator
12
+
13
+ assert_directory @mailer_path = 'app/views/devise/mailer'
14
+ assert_directory @invitations_path = 'app/views/devise/invitations'
15
+ assert_files
16
+ end
17
+
18
+ test 'views can be scoped' do
19
+ run_generator %w(octopussies)
20
+
21
+ assert_directory @mailer_path = 'app/views/octopussies/mailer'
22
+ assert_directory @invitations_path = 'app/views/octopussies/invitations'
23
+ assert_files
24
+ end
25
+
26
+ def teardown
27
+ FileUtils.rm_r Dir['../../tmp/*']
28
+ end
29
+
30
+ private
31
+ def assert_files
32
+ assert views = { @invitations_path => %w/edit.html.erb new.html.erb/, @mailer_path => %w/invitation_instructions.html.erb/ }
33
+
34
+ views.each do |path, files|
35
+ files.each do |file|
36
+ assert_file File.join path, file
37
+ end
38
+ end
39
+ end
40
+ end