revise 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require 'bcrypt'
2
+
3
+ module Revise
4
+ module Models
5
+ module DatabaseAuthenticatable
6
+ extend ActiveSupport::Concern
7
+
8
+ MAILERS = []
9
+ HELPERS = ['Authentication']
10
+ CONTROLLERS = ['Sessions', 'Accounts']
11
+
12
+ included do
13
+ attr_reader :password, :current_password
14
+ attr_accessor :password_confirmation
15
+ end
16
+
17
+ def valid_for_authentication?
18
+ if super && valid_password?
19
+ true
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def self.required_fields(klass)
26
+ [:encrypted_password] + klass.authentication_keys
27
+ end
28
+
29
+ def password=(new_password)
30
+ @password = new_password
31
+ self.encrypted_password = password_digest(@password) if @password.present?
32
+ end
33
+
34
+ def valid_password?(password=nil)
35
+ password = @password if password == nil
36
+
37
+ return false if encrypted_password.blank?
38
+
39
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
40
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
41
+
42
+ String.secure_compare(password, encrypted_password)
43
+ end
44
+
45
+ def clean_up_passwords
46
+ self.password = self.password_confirmation = nil
47
+ end
48
+
49
+ def update_with_password(params, *options)
50
+ current_password = params.delete(:current_password)
51
+
52
+ if params[:password].blank?
53
+ params.delete(:password)
54
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
55
+ end
56
+
57
+ result = if valid_password?(current_password)
58
+ update_attributes(params, *options)
59
+ else
60
+ self.assign_attributes(params, *options)
61
+ self.valid?
62
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
63
+ false
64
+ end
65
+
66
+ clean_up_passwords
67
+ result
68
+ end
69
+
70
+ def update_without_password(params, *options)
71
+ params.delete(:password)
72
+ params.delete(:password_confirmation)
73
+
74
+ result = update_attributes(params, *options)
75
+ clean_up_passwords
76
+ result
77
+ end
78
+
79
+ def after_database_authentication
80
+ end
81
+
82
+ def authenticatable_salt
83
+ encrypted_password[0,29] if encrypted_password
84
+ end
85
+
86
+ protected
87
+
88
+ def password_digest(password)
89
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
90
+ end
91
+
92
+ module ClassMethods
93
+ Revise::Models.config(self, :pepper, :stretches)
94
+
95
+ def authenticate(email, password)
96
+ account = Account.find_by_email(email)
97
+ return false unless account
98
+ if account.valid_password?(password)
99
+ return account
100
+ else
101
+ return false
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,237 @@
1
+ module Revise
2
+ module Models
3
+ module Invitable
4
+ MAILERS = ['Invitable']
5
+ HELPERS = ['Authentication']
6
+ CONTROLLERS = ['Main', 'Sessions', 'Accounts', 'Invitations']
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ attr_accessor :skip_invitation
11
+ attr_accessor :completing_invite
12
+
13
+ included do
14
+ include ::Revise::Inviter
15
+
16
+ if Revise.invited_by_class_name
17
+ belongs_to :invited_by, :class_name => Revise.invited_by_class_name
18
+ else
19
+ belongs_to :invited_by, :polymorphic => true
20
+ end
21
+
22
+ include ActiveSupport::Callbacks
23
+ define_callbacks :invitation_accepted
24
+
25
+ attr_accessor :skip_password
26
+
27
+ scope :invitation_not_accepted, where(:invitation_accepted_at => nil)
28
+ scope :invitation_accepted, where(:invitation_accepted_at.ne => nil)
29
+ end
30
+
31
+ def self.required_fields(klass)
32
+ fields = [:invitation_token, :invitation_sent_at, :invitation_accepted_at, :invitation_limit, :invited_by_id,
33
+ :invited_by_type]
34
+ fields -= [:invited_by_type] if Revise.invited_by_class_name
35
+ fields
36
+ end
37
+
38
+ def invitation_fields
39
+ fields = [:invitation_sent_at, :invited_by_id, :invited_by_type]
40
+ fields -= [:invited_by_type] if Revise.invited_by_class_name
41
+ fields
42
+ end
43
+
44
+ # Accept an invitation by clearing invitation token and and setting invitation_accepted_at
45
+ # Confirms it if model is confirmable
46
+ def accept_invitation!
47
+ self.invitation_accepted_at = Time.now.utc
48
+ if self.invited_to_sign_up? && self.valid?
49
+ run_callbacks :invitation_accepted do
50
+ self.invitation_token = nil
51
+ self.confirmed_at = self.invitation_accepted_at if self.respond_to?(:confirmed_at)
52
+ self.save(:validate => false)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Verifies whether a user has been invited or not
58
+ def invited_to_sign_up?
59
+ invitation_token
60
+ end
61
+
62
+ # Verifies whether a user accepted an invitation (or is accepting it)
63
+ def invitation_accepted?
64
+ invitation_accepted_at
65
+ end
66
+
67
+ # Verifies whether a user has accepted an invitation (or is accepting it), or was never invited
68
+ def accepted_or_not_invited?
69
+ invitation_accepted? || !invited_to_sign_up?
70
+ end
71
+
72
+ # Reset invitation token and send invitation again
73
+ def invite!(invited_by = nil)
74
+ was_invited = invited_to_sign_up?
75
+
76
+ # Required to workaround confirmable model's confirmation_required? method
77
+ # being implemented to check for non-nil value of confirmed_at
78
+ if self.new_record? && self.respond_to?(:confirmation_required?)
79
+ def self.confirmation_required?; false; end
80
+ end
81
+
82
+ generate_invitation_token if self.invitation_token.nil?
83
+ self.invitation_sent_at = Time.now.utc
84
+ self.invited_by = invited_by if invited_by
85
+
86
+ # Call these before_validate methods since we aren't validating on save
87
+ self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys)
88
+ self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace)
89
+
90
+ if save(:validate => false)
91
+ self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
92
+ deliver_invitation unless @skip_invitation
93
+ end
94
+ end
95
+
96
+ # Verify whether a invitation is active or not. If the user has been
97
+ # invited, we need to calculate if the invitation time has not expired
98
+ # for this user, in other words, if the invitation is still valid.
99
+ def valid_invitation?
100
+ invited_to_sign_up? && invitation_period_valid?
101
+ end
102
+
103
+ # Only verify password when is not invited
104
+ def valid_password?(password)
105
+ super unless invited_to_sign_up?
106
+ end
107
+
108
+ def reset_password!(new_password, new_password_confirmation)
109
+ super
110
+ accept_invitation!
111
+ end
112
+
113
+ def invite_key_valid?
114
+ return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
115
+ self.class.invite_key.all? do |key, regexp|
116
+ regexp.nil? || self.send(key).try(:match, regexp)
117
+ end
118
+ end
119
+
120
+ def password_required?
121
+ !@skip_password && (encrypted_password.blank? || password.present?)
122
+ end
123
+
124
+ protected
125
+ # Deliver the invitation email
126
+ def deliver_invitation
127
+ send_revise_notification(:invitable, :invitation_instructions, self.name, self.email, self.invitation_token)
128
+ end
129
+
130
+ # Checks if the invitation for the user is within the limit time.
131
+ # We do this by calculating if the difference between today and the
132
+ # invitation sent date does not exceed the invite for time configured.
133
+ # Invite_for is a model configuration, must always be an integer value.
134
+ #
135
+ # Example:
136
+ #
137
+ # # invite_for = 1.day and invitation_sent_at = today
138
+ # invitation_period_valid? # returns true
139
+ #
140
+ # # invite_for = 5.days and invitation_sent_at = 4.days.ago
141
+ # invitation_period_valid? # returns true
142
+ #
143
+ # # invite_for = 5.days and invitation_sent_at = 5.days.ago
144
+ # invitation_period_valid? # returns false
145
+ #
146
+ # # invite_for = nil
147
+ # invitation_period_valid? # will always return true
148
+ #
149
+ def invitation_period_valid?
150
+ invitation_sent_at && (self.class.invite_for.to_i.zero? || invitation_sent_at.utc >= self.class.invite_for.ago)
151
+ end
152
+
153
+ # Generates a new random token for invitation, and stores the time
154
+ # this token is being generated
155
+ def generate_invitation_token
156
+ self.invitation_token = self.class.invitation_token
157
+ end
158
+
159
+ module ClassMethods
160
+ # Return fields to invite
161
+ def invite_key_fields
162
+ invite_key.keys
163
+ end
164
+
165
+ # Attempt to find a user by it's email. If a record is not found, create a new
166
+ # user and send invitation to it. If user is found, returns the user with an
167
+ # email already exists error.
168
+ # If user is found and still have pending invitation, email is resend unless
169
+ # resend_invitation is set to false
170
+ # Attributes must contain the user email, other attributes will be set in the record
171
+ def _invite(attributes={}, invited_by=nil, &block)
172
+ attributes.symbolize_keys!
173
+ invite_key_array = invite_key_fields
174
+ attributes_hash = {}
175
+ invite_key_array.each do |k,v|
176
+ attributes_hash[k] = attributes.delete(k)
177
+ end
178
+
179
+ invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
180
+ invitable.invited_by = invited_by
181
+
182
+ invitable.skip_password = true
183
+ invitable.valid? if self.validate_on_invite
184
+ if invitable.new_record?
185
+ invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
186
+ elsif !invitable.invited_to_sign_up? || !self.resend_invitation
187
+ invite_key_array.each do |key|
188
+ invitable.errors.add(key, :taken)
189
+ end
190
+ end
191
+
192
+ if invitable.errors.empty?
193
+ yield invitable if block_given?
194
+ mail = invitable.invite!
195
+ end
196
+ [invitable, mail]
197
+ end
198
+
199
+ def invite!(attributes={}, invited_by=nil, &block)
200
+ invitable, mail = _invite(attributes, invited_by, &block)
201
+ invitable
202
+ end
203
+
204
+ def invite_mail!(attributes={}, invited_by=nil, &block)
205
+ invitable, mail = _invite(attributes, invited_by, &block)
206
+ mail
207
+ end
208
+
209
+ # Attempt to find a user by it's invitation_token to set it's password.
210
+ # If a user is found, reset it's password and automatically try saving
211
+ # the record. If not user is found, returns a new user containing an
212
+ # error in invitation_token attribute.
213
+ # Attributes must contain invitation_token, password and confirmation
214
+ def accept_invitation!(attributes={})
215
+ invitable = find_or_initialize_with_error_by(:invitation_token, attributes.delete(:invitation_token))
216
+ invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
217
+ if invitable.errors.empty?
218
+ invitable.attributes = attributes
219
+ invitable.accept_invitation!
220
+ end
221
+ invitable
222
+ end
223
+
224
+ # Generate a token checking if one does not already exist in the database.
225
+ def invitation_token
226
+ generate_token(:invitation_token)
227
+ end
228
+
229
+ Revise::Models.config(self, :invite_for)
230
+ Revise::Models.config(self, :validate_on_invite)
231
+ Revise::Models.config(self, :invitation_limit)
232
+ Revise::Models.config(self, :invite_key)
233
+ Revise::Models.config(self, :resend_invitation)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,99 @@
1
+ module Revise
2
+ module Models
3
+ module Recoverable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = ['Recoverable']
7
+ HELPERS = []
8
+ CONTROLLERS = ['Recovery']
9
+
10
+ def self.required_fields(klass)
11
+ [:reset_password_sent_at, :reset_password_token, :email]
12
+ end
13
+
14
+ def reset_password!(new_password, new_password_confirmation)
15
+ self.password = new_password
16
+ self.password_confirmation = new_password_confirmation
17
+
18
+ if valid?
19
+ clear_reset_password_token
20
+ after_password_reset
21
+ end
22
+
23
+ save
24
+ end
25
+
26
+ def send_reset_password_instructions
27
+ generate_reset_password_token! if should_generate_reset_token?
28
+ send_revise_notification(:recoverable, :reset_password_instructions, self.name, self.email, self.reset_password_token)
29
+ end
30
+
31
+ def reset_password_period_valid?
32
+ reset_password_sent_at && reset_password_sent_at.utc >= self.class.reset_password_within.ago
33
+ end
34
+
35
+ # Generates a new random token for reset password
36
+ def generate_reset_password_token
37
+ self.reset_password_token = self.class.reset_password_token
38
+ self.reset_password_sent_at = Time.now.utc
39
+ self.reset_password_token
40
+ end
41
+
42
+ # Resets the reset password token with and save the record without
43
+ # validating
44
+ def generate_reset_password_token!
45
+ generate_reset_password_token && save(:validate => false)
46
+ end
47
+
48
+ protected
49
+ def should_generate_reset_token?
50
+ reset_password_token.nil? || !reset_password_period_valid?
51
+ end
52
+
53
+ # Removes reset_password token
54
+ def clear_reset_password_token
55
+ self.reset_password_token = nil
56
+ self.reset_password_sent_at = nil
57
+ end
58
+
59
+ def after_password_reset
60
+ end
61
+
62
+ module ClassMethods
63
+ # Attempt to find a user by its email. If a record is found, send new
64
+ # password instructions to it. If not user is found, returns a new user
65
+ # with an email not found error.
66
+ # Attributes must contain the user email
67
+ def send_reset_password_instructions(attributes={})
68
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
69
+ recoverable.send_reset_password_instructions if recoverable.persisted?
70
+ recoverable
71
+ end
72
+
73
+ # Generate a token checking if one does not already exist in the database.
74
+ def reset_password_token
75
+ generate_token(:reset_password_token)
76
+ end
77
+
78
+ # Attempt to find a user by its reset_password_token to reset its
79
+ # password. If a user is found and token is still valid, reset its password and automatically
80
+ # try saving the record. If not user is found, returns a new user
81
+ # containing an error in reset_password_token attribute.
82
+ # Attributes must contain reset_password_token, password and confirmation
83
+ def reset_password_by_token(attributes={})
84
+ recoverable = find_or_initialize_with_error_by(:reset_password_token, attributes[:reset_password_token])
85
+ if recoverable.persisted?
86
+ if recoverable.reset_password_period_valid?
87
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
88
+ else
89
+ recoverable.errors.add(:reset_password_token, :expired)
90
+ end
91
+ end
92
+ recoverable
93
+ end
94
+
95
+ Revise::Models.config(self, :reset_password_keys, :reset_password_within)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ require 'orm_adapter/adapters/mongo_mapper'
2
+ MongoMapper::Document::ClassMethods.send :include, Revise::Models
@@ -0,0 +1,41 @@
1
+ module Revise
2
+ class ParamFilter
3
+ def initialize(case_insensitive_keys, strip_whitespace_keys)
4
+ @case_insensitive_keys = case_insensitive_keys || []
5
+ @strip_whitespace_keys = strip_whitespace_keys || []
6
+ end
7
+
8
+ def filter(conditions)
9
+ conditions = stringify_params(conditions.dup)
10
+
11
+ @case_insensitive_keys.each do |k|
12
+ value = conditions[k]
13
+ next unless value.respond_to?(:downcase)
14
+ conditions[k] = value.downcase
15
+ end
16
+
17
+ @strip_whitespace_keys.each do |k|
18
+ value = conditions[k]
19
+ next unless value.respond_to?(:strip)
20
+ conditions[k] = value.strip
21
+ end
22
+
23
+ conditions
24
+ end
25
+
26
+ # Force keys to be string to avoid injection on mongoid related database.
27
+ def stringify_params(conditions)
28
+ return conditions unless conditions.is_a?(Hash)
29
+ conditions.each do |k, v|
30
+ conditions[k] = v.to_s if param_requires_string_conversion?(v)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Determine which values should be transformed to string or passed as-is to the query builder underneath
37
+ def param_requires_string_conversion?(value)
38
+ [Fixnum, TrueClass, FalseClass, Regexp].none? {|clz| value.is_a? clz }
39
+ end
40
+ end
41
+ end