revise 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ module Revise
2
+ module Helpers
3
+ module Core
4
+ def revise_for(*resources)
5
+ Revise.app = self
6
+
7
+ options = resources.extract_options!
8
+
9
+ resources.each do |resource|
10
+ begin
11
+ if Revise::MODULES.has_key?(resource)
12
+ models = Revise::MODULES[resource]
13
+ models.each do |m|
14
+ model = Revise::Models.const_get(m)
15
+ add_helpers(model)
16
+ add_controllers(model)
17
+ add_mailers(model)
18
+ end
19
+ else
20
+ Padrino.logger.error "Hey man #{resource} doesn't exist"
21
+ end
22
+ rescue Exception => e
23
+ Padrino.logger.error "Failed to load: #{resource} Because #{e.message()}"
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+ def add_helpers(model)
30
+ model::HELPERS.each do |helper|
31
+ self.helpers(Revise::Helpers.const_get(helper))
32
+ end
33
+ end
34
+
35
+ def add_controllers(model)
36
+ model::CONTROLLERS.each do |controller|
37
+ self.send(:extend, Revise::Controllers.const_get(controller))
38
+ end
39
+ end
40
+
41
+ def add_mailers(model)
42
+ model::MAILERS.each do |mailer|
43
+ self.send(:extend, Revise::Mailers.const_get(mailer))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ module Revise
2
+ module Inviter
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ClassMethods
7
+ attr_writer :invitation_limit unless respond_to? :invitation_limit
8
+ end
9
+
10
+ def invitation_limit
11
+ self[:invitation_limit] || self.class.invitation_limit
12
+ end
13
+
14
+ # Return true if this user has invitations left to send
15
+ def has_invitations_left?
16
+ if self.class.invitation_limit.present? || self.respond_to?(:invitation_limit)
17
+ if invitation_limit
18
+ return invitation_limit > 0
19
+ else
20
+ return self.class.invitation_limit > 0 if self.class.invitation_limit.present?
21
+ return false
22
+ end
23
+ else
24
+ return true
25
+ end
26
+ end
27
+
28
+ protected
29
+ def decrement_invitation_limit!
30
+ if self.class.invitation_limit.present?
31
+ self.invitation_limit ||= self.class.invitation_limit
32
+ self.update_attribute(:invitation_limit, invitation_limit - 1)
33
+ end
34
+ end
35
+
36
+ module ClassMethods
37
+ Revise::Models.config(self, :invitation_limit)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ en:
2
+ revise:
3
+ recoverable:
4
+ reset_password_instructions:
5
+ subject: "Reset Password Instructions For %{domain}"
6
+ confirmable:
7
+ confirmation_instructions:
8
+ subject: "Confirm Your Email At %{domain}"
9
+ invitable:
10
+ invation_instructions:
11
+ subject: "You've Been Invited To %{domain}"
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Confirmable
4
+ def self.extended(klass)
5
+ klass.mailer :confirmable do
6
+ email :confirmation_instructions do |name, email, confirmation_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.confirmable.confirmation_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :confirmation_token => confirmation_token
11
+ render 'revise/confirmation_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Invitable
4
+ def self.extended(klass)
5
+ klass.mailer :invitable do
6
+ email :invitation_instructions do |name, email, invitation_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.invitable.invitation_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :invitation_token => invitation_token
11
+ render 'revise/invitation_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Recoverable
4
+ def self.extended(klass)
5
+ klass.mailer :recoverable do
6
+ email :reset_password_instructions do |name, email, reset_password_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.recoverable.reset_password_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :reset_password_token => reset_password_token
11
+ render 'revise/reset_password_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ module Revise
2
+ module Models
3
+ class MissingAttribute < StandardError
4
+ def initialize(attributes)
5
+ @attributes = attributes
6
+ end
7
+
8
+ def message
9
+ "The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
10
+ end
11
+ end
12
+
13
+ def self.config(mod, *accessors)
14
+ class << mod; attr_accessor :available_configs; end
15
+ mod.available_configs = accessors
16
+
17
+ accessors.each do |accessor|
18
+ mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
19
+ def #{accessor}
20
+ if defined?(@#{accessor})
21
+ @#{accessor}
22
+ elsif superclass.respond_to?(:#{accessor})
23
+ superclass.#{accessor}
24
+ else
25
+ Revise.#{accessor}
26
+ end
27
+ end
28
+
29
+ def #{accessor}=(value)
30
+ @#{accessor} = value
31
+ end
32
+ METHOD
33
+ end
34
+ end
35
+
36
+ def self.check_fields!(klass)
37
+ failed_attributes = []
38
+ instance = klass.new
39
+
40
+ klass.revise_modules.each do |mod|
41
+ constant = const_get(mod.to_s.classify)
42
+
43
+ if constant.respond_to?(:required_fields)
44
+ constant.required_fields(klass).each do |field|
45
+ failed_attributes << field unless instance.respond_to?(field)
46
+ end
47
+ else
48
+ ActiveSupport::Deprecation.warn "The module #{mod} doesn't implement self.required_fields(klass). " \
49
+ "Devise uses required_fields to warn developers of any missing fields in their models. " \
50
+ "Please implement #{mod}.required_fields(klass) that returns an array of symbols with the required fields."
51
+ end
52
+ end
53
+
54
+ if failed_attributes.any?
55
+ fail Revise::Models::MissingAttribute.new(failed_attributes)
56
+ end
57
+ end
58
+
59
+ def revise(*modules)
60
+ options = modules.extract_options!.dup
61
+
62
+ plural_name = self.model_name.plural.to_sym
63
+ Revise::MODULES[plural_name] = []
64
+
65
+ revise_modules_hook! do
66
+ include Revise::Models::Authenticatable
67
+
68
+ modules.each do |m|
69
+ mod = Revise::Models.const_get(m.to_s.classify)
70
+
71
+ if mod.const_defined?("ClassMethods")
72
+ class_mod = mod.const_get("ClassMethods")
73
+ extend class_mod
74
+
75
+ if class_mod.respond_to?(:available_configs)
76
+ available_configs = class_mod.available_configs
77
+ available_configs.each do |config|
78
+ next unless options.key?(config)
79
+ send(:"#{config}=", options.delete(config))
80
+ end
81
+ end
82
+ end
83
+
84
+ include mod
85
+
86
+ Revise::MODULES[plural_name] << m.to_s.classify
87
+ end
88
+
89
+ options.each { |key, value| send(:"#{key}=", value) }
90
+ end
91
+ end
92
+
93
+ def revise_modules_hook!
94
+ yield
95
+ end
96
+ end
97
+ end
98
+
99
+ require 'revise/models/authenticatable'
@@ -0,0 +1,137 @@
1
+ module Revise
2
+ module Models
3
+ module Authenticatable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = []
7
+ HELPERS = ['Authentication']
8
+ CONTROLLERS = ['Main', 'Sessions', 'Accounts']
9
+
10
+ BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at, :role,
11
+ :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
12
+ :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
13
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token, :role,
14
+ :roles]
15
+
16
+ included do
17
+ before_validation :downcase_keys
18
+ before_validation :strip_whitespace
19
+ attr_accessor :skip_email
20
+ end
21
+
22
+ def self.required_fields(klass)
23
+ [:role]
24
+ end
25
+
26
+ def valid_for_authentication?
27
+ block_given? ? yield : true
28
+ end
29
+
30
+ def unauthenticated_message
31
+ :invalid
32
+ end
33
+
34
+ def active_for_authentication?
35
+ true
36
+ end
37
+
38
+ def inactive_message
39
+ :inactive
40
+ end
41
+
42
+ def password_required?
43
+ encrypted_password.blank? || password.present?
44
+ end
45
+
46
+ def role?(role)
47
+ return false unless self.respond_to?(:role)
48
+ return self.role.to_sym == role.to_sym
49
+ end
50
+
51
+ array = %w(serializable_hash)
52
+ array << "to_xml"
53
+
54
+ array.each do |method|
55
+ class_eval <<-RUBY, __FILE__, __LINE__
56
+ def #{method}(options=nil)
57
+ options ||= {}
58
+ options[:except] = Array(options[:except])
59
+
60
+ if options[:force_except]
61
+ options[:except].concat Array(options[:force_except])
62
+ else
63
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
64
+ end
65
+ super(options)
66
+ end
67
+ RUBY
68
+ end
69
+
70
+ protected
71
+
72
+ def send_revise_notification(resource, notification, *attributes)
73
+ return false if self.email.blank?
74
+ Revise.app.deliver(resource, notification, *attributes) unless @skip_email
75
+ end
76
+
77
+ def downcase_keys
78
+ self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
79
+ end
80
+
81
+ def strip_whitespace
82
+ self.class.strip_whitespace_keys.each { |k| self[k].try(:strip!) }
83
+ end
84
+
85
+ module ClassMethods
86
+ Revise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
87
+ :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage)
88
+
89
+ def find_for_authentication(conditions)
90
+ find_first_by_auth_conditions(conditions)
91
+ end
92
+
93
+ def find_first_by_auth_conditions(conditions)
94
+ to_adapter.find_first revise_param_filter.filter(conditions)
95
+ end
96
+
97
+ def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
98
+ find_or_initialize_with_errors([attribute], { attribute => value }, error)
99
+ end
100
+
101
+ # Find an initialize a group of attributes based on a list of required attributes.
102
+ def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
103
+ attributes = attributes.slice(*required_attributes)
104
+ attributes.delete_if { |key, value| value.blank? }
105
+
106
+ if attributes.size == required_attributes.size
107
+ record = find_first_by_auth_conditions(attributes)
108
+ end
109
+
110
+ unless record
111
+ record = new
112
+
113
+ required_attributes.each do |key|
114
+ value = attributes[key]
115
+ record.send("#{key}=", value)
116
+ record.errors.add(key, value.present? ? error : :blank)
117
+ end
118
+ end
119
+
120
+ record
121
+ end
122
+
123
+ protected
124
+ def revise_param_filter
125
+ @revise_param_filter ||= Revise::ParamFilter.new(case_insensitive_keys, strip_whitespace_keys)
126
+ end
127
+
128
+ def generate_token(column)
129
+ loop do
130
+ token = String.friendly_token
131
+ break token unless to_adapter.find_first({ column => token })
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,236 @@
1
+ module Revise
2
+ module Models
3
+ module Confirmable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = ['Confirmable']
7
+ HELPERS = []
8
+ CONTROLLERS = ['Confirmations']
9
+
10
+ included do
11
+ before_create :generate_confirmation_token, :if => :confirmation_required?
12
+ after_create :send_on_create_confirmation_instructions, :if => :confirmation_required?
13
+ before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
14
+ after_update :send_confirmation_instructions, :if => :reconfirmation_required?
15
+ end
16
+
17
+ def self.required_fields(klass)
18
+ required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
19
+ required_methods << :unconfirmed_email if klass.reconfirmable
20
+ required_methods
21
+ end
22
+
23
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
24
+ # is already confirmed, add an error to email field. If the user is invalid
25
+ # add errors
26
+ def confirm!
27
+ pending_any_confirmation do
28
+ if confirmation_period_expired?
29
+ self.errors.add(:confirmation_period_expired, 'Token expired')
30
+ return false
31
+ end
32
+
33
+ self.confirmation_token = nil
34
+ self.confirmed_at = Time.now.utc
35
+
36
+ if self.class.reconfirmable && unconfirmed_email.present?
37
+ skip_reconfirmation!
38
+ self.email = unconfirmed_email
39
+ self.unconfirmed_email = nil
40
+
41
+ # We need to validate in such cases to enforce e-mail uniqueness
42
+ save(:validate => true)
43
+ else
44
+ save(:validate => false)
45
+ end
46
+ end
47
+ end
48
+
49
+ # Verifies whether a user is confirmed or not
50
+ def confirmed?
51
+ !!confirmed_at
52
+ end
53
+
54
+ def pending_reconfirmation?
55
+ self.class.reconfirmable && unconfirmed_email.present?
56
+ end
57
+
58
+ # Send confirmation instructions by email
59
+ def send_confirmation_instructions
60
+ self.confirmation_token = nil if reconfirmation_required?
61
+ @reconfirmation_required = false
62
+
63
+ generate_confirmation_token! if self.confirmation_token.blank?
64
+ send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
65
+ end
66
+
67
+ # Resend confirmation token. This method does not need to generate a new token.
68
+ def resend_confirmation_token
69
+ pending_any_confirmation do
70
+ self.confirmation_token = nil if confirmation_period_expired?
71
+ send_confirmation_instructions
72
+ end
73
+ end
74
+
75
+ # Overwrites active_for_authentication? for confirmation
76
+ # by verifying whether a user is active to sign in or not. If the user
77
+ # is already confirmed, it should never be blocked. Otherwise we need to
78
+ # calculate if the confirm time has not expired for this user.
79
+ def active_for_authentication?
80
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
81
+ end
82
+
83
+ # The message to be shown if the account is inactive.
84
+ def inactive_message
85
+ !confirmed? ? :unconfirmed : super
86
+ end
87
+
88
+ # If you don't want confirmation to be sent on create, neither a code
89
+ # to be generated, call skip_confirmation!
90
+ def skip_confirmation!
91
+ self.confirmed_at = Time.now.utc
92
+ end
93
+
94
+ # If you don't want reconfirmation to be sent, neither a code
95
+ # to be generated, call skip_reconfirmation!
96
+ def skip_reconfirmation!
97
+ @bypass_postpone = true
98
+ end
99
+
100
+ protected
101
+
102
+ # A callback method used to deliver confirmation
103
+ # instructions on creation. This can be overriden
104
+ # in models to map to a nice sign up e-mail.
105
+ def send_on_create_confirmation_instructions
106
+ send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
107
+ end
108
+
109
+ # Callback to overwrite if confirmation is required or not.
110
+ def confirmation_required?
111
+ !confirmed?
112
+ end
113
+
114
+ # Checks if the confirmation for the user is within the limit time.
115
+ # We do this by calculating if the difference between today and the
116
+ # confirmation sent date does not exceed the confirm in time configured.
117
+ # Confirm_within is a model configuration, must always be an integer value.
118
+ #
119
+ # Example:
120
+ #
121
+ # # allow_unconfirmed_access_for = 1.day and confirmation_sent_at = today
122
+ # confirmation_period_valid? # returns true
123
+ #
124
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 4.days.ago
125
+ # confirmation_period_valid? # returns true
126
+ #
127
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 5.days.ago
128
+ # confirmation_period_valid? # returns false
129
+ #
130
+ # # allow_unconfirmed_access_for = 0.days
131
+ # confirmation_period_valid? # will always return false
132
+ #
133
+ def confirmation_period_valid?
134
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
135
+ end
136
+
137
+ # Checks if the user confirmation happens before the token becomes invalid
138
+ # Examples:
139
+ #
140
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
141
+ # confirmation_period_expired? # returns false
142
+ #
143
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
144
+ # confirmation_period_expired? # returns true
145
+ #
146
+ # # confirm_within = nil
147
+ # confirmation_period_expired? # will always return false
148
+ #
149
+ def confirmation_period_expired?
150
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
151
+ end
152
+
153
+ # Checks whether the record requires any confirmation.
154
+ def pending_any_confirmation
155
+ if (!confirmed? || pending_reconfirmation?)
156
+ yield
157
+ else
158
+ self.errors.add(:email, :already_confirmed)
159
+ false
160
+ end
161
+ end
162
+
163
+ # Generates a new random token for confirmation, and stores the time
164
+ # this token is being generated
165
+ def generate_confirmation_token
166
+ self.confirmation_token = self.class.confirmation_token
167
+ self.confirmation_sent_at = Time.now.utc
168
+ end
169
+
170
+ def generate_confirmation_token!
171
+ generate_confirmation_token && save(:validate => false)
172
+ end
173
+
174
+ def after_password_reset
175
+ super
176
+ confirm! unless confirmed?
177
+ end
178
+
179
+ def postpone_email_change_until_confirmation
180
+ @reconfirmation_required = true
181
+ self.unconfirmed_email = self.email
182
+ self.email = self.email_was
183
+ end
184
+
185
+ def postpone_email_change?
186
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
187
+ @bypass_postpone = nil
188
+ postpone
189
+ end
190
+
191
+ def reconfirmation_required?
192
+ self.class.reconfirmable && @reconfirmation_required
193
+ end
194
+
195
+ module ClassMethods
196
+ # Attempt to find a user by its email. If a record is found, send new
197
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
198
+ # field. If no user is found, returns a new user with an email not found error.
199
+ # Options must contain the user email
200
+ def send_confirmation_instructions(attributes={})
201
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
202
+ unless confirmable.try(:persisted?)
203
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
204
+ end
205
+ confirmable.resend_confirmation_token if confirmable.persisted?
206
+ confirmable
207
+ end
208
+
209
+ # Find a user by its confirmation token and try to confirm it.
210
+ # If no user is found, returns a new user with an error.
211
+ # If the user is already confirmed, create an error for the user
212
+ # Options must have the confirmation_token
213
+ def confirm_by_token(confirmation_token)
214
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
215
+ confirmable.confirm! if confirmable.persisted?
216
+ confirmable
217
+ end
218
+
219
+ # Generate a token checking if one does not already exist in the database.
220
+ def confirmation_token
221
+ generate_token(:confirmation_token)
222
+ end
223
+
224
+ # Find a record for confirmation by unconfirmed email field
225
+ def find_by_unconfirmed_email_with_errors(attributes = {})
226
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
227
+ unconfirmed_attributes = attributes.symbolize_keys
228
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
229
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
230
+ end
231
+
232
+ Revise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
233
+ end
234
+ end
235
+ end
236
+ end