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.
- data/CHANGELOG +5 -0
- data/README.rdoc +39 -12
- data/app/controllers/devise/invitations_controller.rb +20 -6
- data/app/controllers/devise_invitable/registrations_controller.rb +2 -2
- data/app/views/devise/mailer/invitation_instructions.html.erb +1 -1
- data/lib/devise_invitable/mailer.rb +3 -7
- data/lib/devise_invitable/model.rb +53 -59
- data/lib/devise_invitable/parameter_sanitizer.rb +11 -0
- data/lib/devise_invitable/rails.rb +1 -0
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/devise_invitable.rb +1 -0
- data/lib/generators/active_record/templates/migration.rb +3 -2
- data/test/functional/controller_helpers_test.rb +39 -0
- data/test/functional/registrations_controller_test.rb +59 -0
- data/test/generators/views_generator_test.rb +40 -0
- data/test/generators_test.rb +34 -0
- data/test/integration/invitation_remove_test.rb +29 -0
- data/test/integration/invitation_test.rb +222 -0
- data/test/integration_tests_helper.rb +48 -0
- data/test/mailers/invitation_mail_test.rb +69 -0
- data/test/model_tests_helper.rb +33 -0
- data/test/models/invitable_test.rb +558 -0
- data/test/models_test.rb +74 -0
- data/test/orm/active_record.rb +4 -0
- data/test/orm/mongoid.rb +20 -0
- data/test/rails_app/Rakefile +7 -0
- data/test/rails_app/app/controllers/admins_controller.rb +6 -0
- data/test/rails_app/app/controllers/application_controller.rb +10 -0
- data/test/rails_app/app/controllers/free_invitations_controller.rb +6 -0
- data/test/rails_app/app/controllers/home_controller.rb +4 -0
- data/test/rails_app/app/controllers/users_controller.rb +12 -0
- data/test/rails_app/app/helpers/application_helper.rb +2 -0
- data/test/rails_app/app/models/admin.rb +23 -0
- data/test/rails_app/app/models/octopussy.rb +15 -0
- data/test/rails_app/app/models/user.rb +56 -0
- data/test/rails_app/app/views/admins/new.html.erb +12 -0
- data/test/rails_app/app/views/free_invitations/new.html.erb +12 -0
- data/test/rails_app/app/views/home/index.html.erb +0 -0
- data/test/rails_app/app/views/layouts/application.html.erb +16 -0
- data/test/rails_app/app/views/users/invitations/new.html.erb +15 -0
- data/test/rails_app/config/application.rb +24 -0
- data/test/rails_app/config/boot.rb +11 -0
- data/test/rails_app/config/database.yml +22 -0
- data/test/rails_app/config/environment.rb +5 -0
- data/test/rails_app/config/environments/development.rb +25 -0
- data/test/rails_app/config/environments/production.rb +49 -0
- data/test/rails_app/config/environments/test.rb +33 -0
- data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails_app/config/initializers/devise.rb +213 -0
- data/test/rails_app/config/initializers/inflections.rb +10 -0
- data/test/rails_app/config/initializers/mime_types.rb +5 -0
- data/test/rails_app/config/initializers/secret_token.rb +7 -0
- data/test/rails_app/config/initializers/session_store.rb +8 -0
- data/test/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/test/rails_app/config/locales/devise.en.yml +57 -0
- data/test/rails_app/config/locales/en.yml +14 -0
- data/test/rails_app/config/routes.rb +9 -0
- data/test/rails_app/config.ru +4 -0
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +39 -0
- data/test/rails_app/mongoid.yml +10 -0
- data/test/rails_app/script/rails +6 -0
- data/test/routes_test.rb +20 -0
- data/test/test_helper.rb +24 -0
- metadata +135 -47
- data/app/controllers/devise_invitable/registrations_controller.rb~ +0 -15
- data/lib/devise_invitable/controllers/helpers.rb~ +0 -21
- data/lib/devise_invitable/controllers/registrations.rb~ +0 -21
- data/lib/devise_invitable/model.rb~ +0 -224
- data/lib/devise_invitable/rails.rb~ +0 -21
- 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
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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!(
|
|
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
|
-
|
|
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.
|
|
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(
|
|
5
|
-
hash
|
|
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 => @
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
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.
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
225
|
-
# user and send invitation to it. If user is found,
|
|
226
|
-
# email already exists error.
|
|
227
|
-
# If user is found and still
|
|
228
|
-
# resend_invitation is set to false
|
|
229
|
-
# Attributes must contain the user email, other attributes will be
|
|
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
|
|
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.
|
|
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
|
|
252
|
-
|
|
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
|
-
|
|
281
|
-
invitable
|
|
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
|
|
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)
|
|
@@ -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
|
data/lib/devise_invitable.rb
CHANGED
|
@@ -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
|
|
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
|