kingsman 0.0.0.beta → 0.1.1

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +5 -4
  4. data/app/controllers/kingsman/confirmations_controller.rb +54 -0
  5. data/app/controllers/kingsman/omniauth_callbacks_controller.rb +36 -0
  6. data/app/controllers/kingsman/passwords_controller.rb +83 -0
  7. data/app/controllers/kingsman/registrations_controller.rb +168 -0
  8. data/app/controllers/kingsman/sessions_controller.rb +83 -0
  9. data/app/controllers/kingsman/unlocks_controller.rb +52 -0
  10. data/app/controllers/kingsman_controller.rb +252 -0
  11. data/app/jobs/application_job.rb +2 -0
  12. data/app/mailers/kingsman/mailer.rb +30 -0
  13. data/app/views/kingsman/confirmations/new.html.erb +16 -0
  14. data/app/views/kingsman/home/index.html.erb +1 -0
  15. data/app/views/kingsman/mailer/confirmation_instructions.html.erb +5 -0
  16. data/app/views/kingsman/mailer/email_changed.html.erb +7 -0
  17. data/app/views/kingsman/mailer/password_change.html.erb +3 -0
  18. data/app/views/kingsman/mailer/reset_password_instructions.html.erb +8 -0
  19. data/app/views/kingsman/mailer/unlock_instructions.html.erb +7 -0
  20. data/app/views/kingsman/passwords/edit.html.erb +25 -0
  21. data/app/views/kingsman/passwords/new.html.erb +16 -0
  22. data/app/views/kingsman/registrations/edit.html.erb +42 -0
  23. data/app/views/kingsman/registrations/new.html.erb +29 -0
  24. data/app/views/kingsman/sessions/new.html.erb +26 -0
  25. data/app/views/kingsman/shared/_error_messages.html.erb +15 -0
  26. data/app/views/kingsman/shared/_links.html.erb +25 -0
  27. data/app/views/kingsman/unlocks/new.html.erb +16 -0
  28. data/app/views/kingsman/up/index.html.erb +11 -0
  29. data/config/application.rb +0 -0
  30. data/config/locales/en.yml +63 -0
  31. data/config.ru +6 -0
  32. data/lib/generators/active_record/kingsman_generator.rb +127 -0
  33. data/lib/generators/active_record/templates/migration.rb +20 -0
  34. data/lib/generators/active_record/templates/migration_existing.rb +27 -0
  35. data/lib/generators/kingsman/controllers_generator.rb +46 -0
  36. data/lib/generators/kingsman/install_generator.rb +42 -0
  37. data/lib/generators/kingsman/kingsman_generator.rb +28 -0
  38. data/lib/generators/kingsman/orm_helpers.rb +40 -0
  39. data/lib/generators/kingsman/views_generator.rb +145 -0
  40. data/lib/generators/mongoid/kingsman_generator.rb +57 -0
  41. data/lib/generators/templates/README +36 -0
  42. data/lib/generators/templates/controllers/README +14 -0
  43. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  44. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  45. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  46. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  47. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  48. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  49. data/lib/generators/templates/kingsman.rb +313 -0
  50. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  51. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  52. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  53. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  54. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  55. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +20 -0
  56. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +27 -0
  57. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +18 -0
  58. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +35 -0
  59. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +25 -0
  60. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +20 -0
  61. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +19 -0
  62. data/lib/kingsman/autoloader.rb +31 -0
  63. data/lib/kingsman/controllers/helpers.rb +221 -0
  64. data/lib/kingsman/controllers/rememberable.rb +57 -0
  65. data/lib/kingsman/controllers/responder.rb +35 -0
  66. data/lib/kingsman/controllers/scoped_views.rb +19 -0
  67. data/lib/kingsman/controllers/sign_in_out.rb +112 -0
  68. data/lib/kingsman/controllers/store_location.rb +76 -0
  69. data/lib/kingsman/controllers/url_helpers.rb +72 -0
  70. data/lib/kingsman/delegator.rb +18 -0
  71. data/lib/kingsman/encryptor.rb +19 -0
  72. data/lib/kingsman/failure_app.rb +280 -0
  73. data/lib/kingsman/hooks/activatable.rb +12 -0
  74. data/lib/kingsman/hooks/csrf_cleaner.rb +14 -0
  75. data/lib/kingsman/hooks/forgetable.rb +11 -0
  76. data/lib/kingsman/hooks/lockable.rb +9 -0
  77. data/lib/kingsman/hooks/proxy.rb +23 -0
  78. data/lib/kingsman/hooks/rememberable.rb +9 -0
  79. data/lib/kingsman/hooks/timeoutable.rb +35 -0
  80. data/lib/kingsman/hooks/trackable.rb +11 -0
  81. data/lib/kingsman/hooks.rb +6 -0
  82. data/lib/kingsman/jets/routes.rb +195 -0
  83. data/lib/kingsman/jets/warden_compat.rb +15 -0
  84. data/lib/kingsman/jets.rb +39 -0
  85. data/lib/kingsman/mailers/helpers.rb +93 -0
  86. data/lib/kingsman/mapping.rb +148 -0
  87. data/lib/kingsman/models/authenticatable.rb +310 -0
  88. data/lib/kingsman/models/confirmable.rb +369 -0
  89. data/lib/kingsman/models/database_authenticatable.rb +206 -0
  90. data/lib/kingsman/models/lockable.rb +214 -0
  91. data/lib/kingsman/models/omniauthable.rb +29 -0
  92. data/lib/kingsman/models/recoverable.rb +156 -0
  93. data/lib/kingsman/models/registerable.rb +29 -0
  94. data/lib/kingsman/models/rememberable.rb +158 -0
  95. data/lib/kingsman/models/timeoutable.rb +45 -0
  96. data/lib/kingsman/models/trackable.rb +51 -0
  97. data/lib/kingsman/models/validatable.rb +68 -0
  98. data/lib/kingsman/models.rb +122 -0
  99. data/lib/kingsman/modules.rb +33 -0
  100. data/lib/kingsman/omniauth/config.rb +47 -0
  101. data/lib/kingsman/omniauth/url_helpers.rb +28 -0
  102. data/lib/kingsman/omniauth.rb +20 -0
  103. data/lib/kingsman/orm/active_record.rb +13 -0
  104. data/lib/kingsman/orm/mongoid.rb +8 -0
  105. data/lib/kingsman/orm.rb +37 -0
  106. data/lib/kingsman/parameter_filter.rb +44 -0
  107. data/lib/kingsman/parameter_sanitizer.rb +173 -0
  108. data/lib/kingsman/secret_key_finder.rb +27 -0
  109. data/lib/kingsman/strategies/authenticatable.rb +178 -0
  110. data/lib/kingsman/strategies/base.rb +22 -0
  111. data/lib/kingsman/strategies/database_authenticatable.rb +31 -0
  112. data/lib/kingsman/strategies/rememberable.rb +67 -0
  113. data/lib/kingsman/token_generator.rb +32 -0
  114. data/lib/kingsman/version.rb +1 -1
  115. data/lib/kingsman.rb +427 -3
  116. metadata +304 -11
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module Mailers
5
+ module Helpers
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Kingsman::Controllers::ScopedViews
10
+ end
11
+
12
+ protected
13
+
14
+ attr_reader :scope_name, :resource
15
+
16
+ # Configure default email options
17
+ def kingsman_mail(record, action, opts = {}, &block)
18
+ initialize_from_record(record)
19
+ mail headers_for(action, opts), &block
20
+ end
21
+
22
+ def initialize_from_record(record)
23
+ @scope_name = Kingsman::Mapping.find_scope!(record)
24
+ @resource = instance_variable_set("@#{kingsman_mapping.name}", record)
25
+ end
26
+
27
+ def kingsman_mapping
28
+ @kingsman_mapping ||= Kingsman.mappings[scope_name]
29
+ end
30
+
31
+ def headers_for(action, opts)
32
+ headers = {
33
+ subject: subject_for(action),
34
+ to: resource.email,
35
+ from: mailer_sender(kingsman_mapping),
36
+ reply_to: mailer_reply_to(kingsman_mapping),
37
+ template_path: template_paths,
38
+ template_name: action
39
+ }.merge(opts)
40
+
41
+ @email = headers[:to]
42
+ headers
43
+ end
44
+
45
+ def mailer_reply_to(mapping)
46
+ mailer_sender(mapping, :reply_to)
47
+ end
48
+
49
+ def mailer_from(mapping)
50
+ mailer_sender(mapping, :from)
51
+ end
52
+
53
+ def mailer_sender(mapping, sender = :from)
54
+ default_sender = default_params[sender]
55
+ if default_sender.present?
56
+ default_sender.respond_to?(:to_proc) ? instance_eval(&default_sender) : default_sender
57
+ elsif Kingsman.mailer_sender.is_a?(Proc)
58
+ Kingsman.mailer_sender.call(mapping.name)
59
+ else
60
+ Kingsman.mailer_sender
61
+ end
62
+ end
63
+
64
+ def template_paths
65
+ template_path = _prefixes.dup
66
+ template_path.unshift "#{@kingsman_mapping.scoped_path}/mailer" if self.class.scoped_views?
67
+ template_path
68
+ end
69
+
70
+ # Set up a subject doing an I18n lookup. At first, it attempts to set a subject
71
+ # based on the current mapping:
72
+ #
73
+ # en:
74
+ # kingsman:
75
+ # mailer:
76
+ # confirmation_instructions:
77
+ # user_subject: '...'
78
+ #
79
+ # If one does not exist, it fallbacks to ActionMailer default:
80
+ #
81
+ # en:
82
+ # kingsman:
83
+ # mailer:
84
+ # confirmation_instructions:
85
+ # subject: '...'
86
+ #
87
+ def subject_for(key)
88
+ I18n.t(:"#{kingsman_mapping.name}_subject", scope: [:kingsman, :mailer, key],
89
+ default: [:subject, key.to_s.humanize])
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,148 @@
1
+ module Kingsman
2
+ class Mapping
3
+ attr_reader :singular, :scoped_path, :path, :controllers, :path_names,
4
+ :class_name, :sign_out_via, :format, :used_routes, :used_helpers,
5
+ :failure_app, :router_name
6
+ attr_reader :name
7
+
8
+ # Receives an object and find a scope for it. If a scope cannot be found,
9
+ # raises an error. If a symbol is given, it's considered to be the scope.
10
+ def self.find_scope!(obj)
11
+ obj = obj.kingsman_scope if obj.respond_to?(:kingsman_scope)
12
+ case obj
13
+ when String, Symbol
14
+ return obj.to_sym
15
+ when Class
16
+ Kingsman.mappings.each_value { |m| return m.name if obj <= m.to }
17
+ else
18
+ Kingsman.mappings.each_value { |m| return m.name if obj.is_a?(m.to) }
19
+ end
20
+
21
+ raise "Could not find a valid mapping for #{obj.inspect}"
22
+ end
23
+
24
+ def self.find_by_path!(path, path_type = :fullpath)
25
+ Kingsman.mappings.each_value { |m| return m if path.include?(m.send(path_type)) }
26
+ raise "Could not find a valid mapping for path #{path.inspect}"
27
+ end
28
+
29
+ def initialize(name, options)
30
+ @scoped_path = options[:as] ? "#{options[:as]}/#{name}" : name.to_s
31
+ @name = name.to_s.singularize.to_sym
32
+ @class_name = (options[:class_name] || name.to_s.classify).to_s
33
+
34
+ @path = (options[:path] || name).to_s
35
+ @path_prefix = options[:path_prefix]
36
+
37
+ @sign_out_via = options[:sign_out_via] || Kingsman.sign_out_via
38
+ @format = options[:format]
39
+
40
+ # default_failure_app(options)
41
+ default_controllers(options)
42
+ default_path_names(options)
43
+ default_used_route(options)
44
+ # default_used_helpers(options)
45
+ end
46
+
47
+ # Return modules for the mapping.
48
+ def modules
49
+ @modules ||= to.respond_to?(:kingsman_modules) ? to.kingsman_modules : []
50
+ end
51
+
52
+ def to
53
+ @class_name.constantize
54
+ end
55
+
56
+ def strategies
57
+ @strategies ||= STRATEGIES.values_at(*self.modules).compact.uniq.reverse
58
+ end
59
+
60
+ def no_input_strategies
61
+ self.strategies & Kingsman::NO_INPUT
62
+ end
63
+
64
+ def routes
65
+ @routes ||= ROUTES.values_at(*self.modules).compact.uniq
66
+ end
67
+
68
+ def authenticatable?
69
+ @authenticatable ||= self.modules.any? { |m| m.to_s =~ /authenticatable/ }
70
+ end
71
+
72
+ def fullpath
73
+ "/#{@path_prefix}/#{@path}".squeeze("/")
74
+ end
75
+
76
+ # Create magic predicates for verifying what module is activated by this map.
77
+ # Example:
78
+ #
79
+ # def confirmable?
80
+ # self.modules.include?(:confirmable)
81
+ # end
82
+ #
83
+ def self.add_module(m)
84
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
85
+ def #{m}?
86
+ self.modules.include?(:#{m})
87
+ end
88
+ METHOD
89
+ end
90
+
91
+ private
92
+
93
+ def default_failure_app(options)
94
+ @failure_app = options[:failure_app] || Kingsman::FailureApp
95
+ if @failure_app.is_a?(String)
96
+ ref = @failure_app.constantize
97
+ @failure_app = lambda { |env| ref.call(env) }
98
+ end
99
+ end
100
+
101
+ def default_controllers(options)
102
+ mod = options[:module] || "kingsman"
103
+ @controllers = Hash.new { |h,k| h[k] = "#{mod}/#{k}" }
104
+ @controllers.merge!(options[:controllers]) if options[:controllers]
105
+ @controllers.each { |k,v| @controllers[k] = v.to_s }
106
+ end
107
+
108
+ def default_path_names(options)
109
+ @path_names = Hash.new { |h,k| h[k] = k.to_s }
110
+ @path_names[:registration] = ""
111
+ @path_names.merge!(options[:path_names]) if options[:path_names]
112
+ end
113
+
114
+ def default_constraints(options)
115
+ @constraints = Hash.new
116
+ @constraints.merge!(options[:constraints]) if options[:constraints]
117
+ end
118
+
119
+ def default_defaults(options)
120
+ @defaults = Hash.new
121
+ @defaults.merge!(options[:defaults]) if options[:defaults]
122
+ end
123
+
124
+ def default_used_route(options)
125
+ singularizer = lambda { |s| s.to_s.singularize.to_sym }
126
+
127
+ if options.has_key?(:only)
128
+ @used_routes = self.routes & Array(options[:only]).map(&singularizer)
129
+ elsif options[:skip] == :all
130
+ @used_routes = []
131
+ else
132
+ @used_routes = self.routes - Array(options[:skip]).map(&singularizer)
133
+ end
134
+ end
135
+
136
+ def default_used_helpers(options)
137
+ singularizer = lambda { |s| s.to_s.singularize.to_sym }
138
+
139
+ if options[:skip_helpers] == true
140
+ @used_helpers = @used_routes
141
+ elsif skip = options[:skip_helpers]
142
+ @used_helpers = self.routes - Array(skip).map(&singularizer)
143
+ else
144
+ @used_helpers = self.routes
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,310 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kingsman/hooks/activatable'
4
+ require 'kingsman/hooks/csrf_cleaner'
5
+
6
+ module Kingsman
7
+ module Models
8
+ # Authenticatable module. Holds common settings for authentication.
9
+ #
10
+ # == Options
11
+ #
12
+ # Authenticatable adds the following options to +kingsman+:
13
+ #
14
+ # * +authentication_keys+: parameters used for authentication. By default [:email].
15
+ #
16
+ # * +http_authentication_key+: map the username passed via HTTP Auth to this parameter. Defaults to
17
+ # the first element in +authentication_keys+.
18
+ #
19
+ # * +request_keys+: parameters from the request object used for authentication.
20
+ # By specifying a symbol (which should be a request method), it will automatically be
21
+ # passed to find_for_authentication method and considered in your model lookup.
22
+ #
23
+ # For instance, if you set :request_keys to [:subdomain], :subdomain will be considered
24
+ # as key on authentication. This can also be a hash where the value is a boolean specifying
25
+ # if the value is required or not.
26
+ #
27
+ # * +http_authenticatable+: if this model allows http authentication. By default false.
28
+ # It also accepts an array specifying the strategies that should allow http.
29
+ #
30
+ # * +params_authenticatable+: if this model allows authentication through request params. By default true.
31
+ # It also accepts an array specifying the strategies that should allow params authentication.
32
+ #
33
+ # * +skip_session_storage+: By default Kingsman will store the user in session.
34
+ # By default is set to skip_session_storage: [:http_auth].
35
+ #
36
+ # == active_for_authentication?
37
+ #
38
+ # After authenticating a user and in each request, Kingsman checks if your model is active by
39
+ # calling model.active_for_authentication?. This method is overwritten by other kingsman modules. For instance,
40
+ # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
41
+ #
42
+ # You can overwrite this method yourself, but if you do, don't forget to call super:
43
+ #
44
+ # def active_for_authentication?
45
+ # super && special_condition_is_valid?
46
+ # end
47
+ #
48
+ # Whenever active_for_authentication? returns false, Kingsman asks the reason why your model is inactive using
49
+ # the inactive_message method. You can overwrite it as well:
50
+ #
51
+ # def inactive_message
52
+ # special_condition_is_valid? ? super : :special_condition_is_not_valid
53
+ # end
54
+ #
55
+ module Authenticatable
56
+ extend ActiveSupport::Concern
57
+
58
+ UNSAFE_ATTRIBUTES_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
59
+ :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
60
+ :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
61
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
62
+
63
+ included do
64
+ class_attribute :kingsman_modules, instance_writer: false
65
+ self.kingsman_modules ||= []
66
+
67
+ before_validation :downcase_keys
68
+ before_validation :strip_whitespace
69
+ end
70
+
71
+ def self.required_fields(klass)
72
+ []
73
+ end
74
+
75
+ # Check if the current object is valid for authentication. This method and
76
+ # find_for_authentication are the methods used in a Warden::Strategy to check
77
+ # if a model should be signed in or not.
78
+ #
79
+ # However, you should not overwrite this method, you should overwrite active_for_authentication?
80
+ # and inactive_message instead.
81
+ def valid_for_authentication?
82
+ block_given? ? yield : true
83
+ end
84
+
85
+ def unauthenticated_message
86
+ :invalid
87
+ end
88
+
89
+ def active_for_authentication?
90
+ true
91
+ end
92
+
93
+ def inactive_message
94
+ :inactive
95
+ end
96
+
97
+ def authenticatable_salt
98
+ end
99
+
100
+ # Redefine serializable_hash in models for more secure defaults.
101
+ # By default, it removes from the serializable model all attributes that
102
+ # are *not* accessible. You can remove this default by using :force_except
103
+ # and passing a new list of attributes you want to exempt. All attributes
104
+ # given to :except will simply add names to exempt to Kingsman internal list.
105
+ def serializable_hash(options = nil)
106
+ options = options.try(:dup) || {}
107
+ options[:except] = Array(options[:except]).dup
108
+
109
+ if options[:force_except]
110
+ options[:except].concat Array(options[:force_except])
111
+ else
112
+ options[:except].concat UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
113
+ end
114
+
115
+ super(options)
116
+ end
117
+
118
+ # Redefine inspect using serializable_hash, to ensure we don't accidentally
119
+ # leak passwords into exceptions.
120
+ def inspect
121
+ inspection = serializable_hash.collect do |k,v|
122
+ "#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
123
+ end
124
+ "#<#{self.class} #{inspection.join(", ")}>"
125
+ end
126
+
127
+ protected
128
+
129
+ def kingsman_mailer
130
+ Kingsman.mailer
131
+ end
132
+
133
+ # This is an internal method called every time Kingsman needs
134
+ # to send a notification/mail. This can be overridden if you
135
+ # need to customize the e-mail delivery logic. For instance,
136
+ # if you are using a queue to deliver e-mails (active job, delayed
137
+ # job, sidekiq, resque, etc), you must add the delivery to the queue
138
+ # just after the transaction was committed. To achieve this,
139
+ # you can override send_kingsman_notification to store the
140
+ # deliveries until the after_commit callback is triggered.
141
+ #
142
+ # The following example uses Active Job's `deliver_later` :
143
+ #
144
+ # class User
145
+ # kingsman :database_authenticatable, :confirmable
146
+ #
147
+ # after_commit :send_pending_kingsman_notifications
148
+ #
149
+ # protected
150
+ #
151
+ # def send_kingsman_notification(notification, *args)
152
+ # # If the record is new or changed then delay the
153
+ # # delivery until the after_commit callback otherwise
154
+ # # send now because after_commit will not be called.
155
+ # # For Rails < 6 use `changed?` instead of `saved_changes?`.
156
+ # if new_record? || saved_changes?
157
+ # pending_kingsman_notifications << [notification, args]
158
+ # else
159
+ # render_and_send_kingsman_message(notification, *args)
160
+ # end
161
+ # end
162
+ #
163
+ # private
164
+ #
165
+ # def send_pending_kingsman_notifications
166
+ # pending_kingsman_notifications.each do |notification, args|
167
+ # render_and_send_kingsman_message(notification, *args)
168
+ # end
169
+ #
170
+ # # Empty the pending notifications array because the
171
+ # # after_commit hook can be called multiple times which
172
+ # # could cause multiple emails to be sent.
173
+ # pending_kingsman_notifications.clear
174
+ # end
175
+ #
176
+ # def pending_kingsman_notifications
177
+ # @pending_kingsman_notifications ||= []
178
+ # end
179
+ #
180
+ # def render_and_send_kingsman_message(notification, *args)
181
+ # message = kingsman_mailer.send(notification, self, *args)
182
+ #
183
+ # # Deliver later with Active Job's `deliver_later`
184
+ # if message.respond_to?(:deliver_later)
185
+ # message.deliver_later
186
+ # # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
187
+ # elsif message.respond_to?(:deliver_now)
188
+ # message.deliver_now
189
+ # else
190
+ # message.deliver
191
+ # end
192
+ # end
193
+ #
194
+ # end
195
+ #
196
+ def send_kingsman_notification(notification, *args)
197
+ message = kingsman_mailer.send(notification, self, *args)
198
+ # Remove once we move to Rails 4.2+ only.
199
+ if message.respond_to?(:deliver_now)
200
+ message.deliver_now
201
+ else
202
+ message.deliver
203
+ end
204
+ end
205
+
206
+ def downcase_keys
207
+ self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase) }
208
+ end
209
+
210
+ def strip_whitespace
211
+ self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip) }
212
+ end
213
+
214
+ def apply_to_attribute_or_variable(attr, method)
215
+ if self[attr]
216
+ self[attr] = self[attr].try(method)
217
+
218
+ # Use respond_to? here to avoid a regression where globally
219
+ # configured strip_whitespace_keys or case_insensitive_keys were
220
+ # attempting to strip or downcase when a model didn't have the
221
+ # globally configured key.
222
+ elsif respond_to?(attr) && respond_to?("#{attr}=")
223
+ new_value = send(attr).try(method)
224
+ send("#{attr}=", new_value)
225
+ end
226
+ end
227
+
228
+ module ClassMethods
229
+ Kingsman::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
230
+ :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage,
231
+ :http_authentication_key)
232
+
233
+ def serialize_into_session(record)
234
+ [record.to_key, record.authenticatable_salt]
235
+ end
236
+
237
+ def serialize_from_session(key, salt)
238
+ record = to_adapter.get(key)
239
+ record if record && record.authenticatable_salt == salt
240
+ end
241
+
242
+ def params_authenticatable?(strategy)
243
+ params_authenticatable.is_a?(Array) ?
244
+ params_authenticatable.include?(strategy) : params_authenticatable
245
+ end
246
+
247
+ def http_authenticatable?(strategy)
248
+ http_authenticatable.is_a?(Array) ?
249
+ http_authenticatable.include?(strategy) : http_authenticatable
250
+ end
251
+
252
+ # Find first record based on conditions given (ie by the sign in form).
253
+ # This method is always called during an authentication process but
254
+ # it may be wrapped as well. For instance, database authenticatable
255
+ # provides a `find_for_database_authentication` that wraps a call to
256
+ # this method. This allows you to customize both database authenticatable
257
+ # or the whole authenticate stack by customize `find_for_authentication.`
258
+ #
259
+ # Overwrite to add customized conditions, create a join, or maybe use a
260
+ # namedscope to filter records while authenticating.
261
+ # Example:
262
+ #
263
+ # def self.find_for_authentication(tainted_conditions)
264
+ # find_first_by_auth_conditions(tainted_conditions, active: true)
265
+ # end
266
+ #
267
+ # Finally, notice that Kingsman also queries for users in other scenarios
268
+ # besides authentication, for example when retrieving a user to send
269
+ # an e-mail for password reset. In such cases, find_for_authentication
270
+ # is not called.
271
+ def find_for_authentication(tainted_conditions)
272
+ find_first_by_auth_conditions(tainted_conditions)
273
+ end
274
+
275
+ def find_first_by_auth_conditions(tainted_conditions, opts = {})
276
+ to_adapter.find_first(kingsman_parameter_filter.filter(tainted_conditions).merge(opts))
277
+ end
278
+
279
+ # Find or initialize a record setting an error if it can't be found.
280
+ def find_or_initialize_with_error_by(attribute, value, error = :invalid) #:nodoc:
281
+ find_or_initialize_with_errors([attribute], { attribute => value }, error)
282
+ end
283
+
284
+ # Find or initialize a record with group of attributes based on a list of required attributes.
285
+ def find_or_initialize_with_errors(required_attributes, attributes, error = :invalid) #:nodoc:
286
+ attributes.try(:permit!)
287
+ attributes = attributes.to_h.with_indifferent_access
288
+ .slice(*required_attributes)
289
+ .delete_if { |key, value| value.blank? }
290
+
291
+ if attributes.size == required_attributes.size
292
+ record = find_first_by_auth_conditions(attributes) and return record
293
+ end
294
+
295
+ new(kingsman_parameter_filter.filter(attributes)).tap do |record|
296
+ required_attributes.each do |key|
297
+ record.errors.add(key, attributes[key].blank? ? :blank : error)
298
+ end
299
+ end
300
+ end
301
+
302
+ protected
303
+
304
+ def kingsman_parameter_filter
305
+ @kingsman_parameter_filter ||= Kingsman::ParameterFilter.new(case_insensitive_keys, strip_whitespace_keys)
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end