kingsman 0.0.0.beta → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module Models
5
+ # Validatable creates all needed validations for a user email and password.
6
+ # It's optional, given you may want to create the validations by yourself.
7
+ # Automatically validate if the email is present, unique and its format is
8
+ # valid. Also tests presence of password, confirmation and length.
9
+ #
10
+ # == Options
11
+ #
12
+ # Validatable adds the following options to +kingsman+:
13
+ #
14
+ # * +email_regexp+: the regular expression used to validate e-mails;
15
+ # * +password_length+: a range expressing password length. Defaults to 6..128.
16
+ #
17
+ module Validatable
18
+ # All validations used by this module.
19
+ VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
20
+ :validates_confirmation_of, :validates_length_of].freeze
21
+
22
+ def self.required_fields(klass)
23
+ []
24
+ end
25
+
26
+ def self.included(base)
27
+ base.extend ClassMethods
28
+ assert_validations_api!(base)
29
+
30
+ base.class_eval do
31
+ validates_presence_of :email, if: :email_required?
32
+ validates_uniqueness_of :email, allow_blank: true, case_sensitive: true, if: :kingsman_will_save_change_to_email?
33
+ validates_format_of :email, with: email_regexp, allow_blank: true, if: :kingsman_will_save_change_to_email?
34
+
35
+ validates_presence_of :password, if: :password_required?
36
+ validates_confirmation_of :password, if: :password_required?
37
+ validates_length_of :password, within: password_length, allow_blank: true
38
+ end
39
+ end
40
+
41
+ def self.assert_validations_api!(base) #:nodoc:
42
+ unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
43
+
44
+ unless unavailable_validations.empty?
45
+ raise "Could not use :validatable module since #{base} does not respond " \
46
+ "to the following methods: #{unavailable_validations.to_sentence}."
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ # Checks whether a password is needed or not. For validations only.
53
+ # Passwords are always required if it's a new record, or if the password
54
+ # or confirmation are being set somewhere.
55
+ def password_required?
56
+ !persisted? || !password.nil? || !password_confirmation.nil?
57
+ end
58
+
59
+ def email_required?
60
+ true
61
+ end
62
+
63
+ module ClassMethods
64
+ Kingsman::Models.config(self, :email_regexp, :password_length)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module Models
5
+ class MissingAttribute < StandardError
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def message
11
+ "The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
12
+ end
13
+ end
14
+
15
+ # Creates configuration values for Kingsman and for the given module.
16
+ #
17
+ # Kingsman::Models.config(Kingsman::Models::DatabaseAuthenticatable, :stretches)
18
+ #
19
+ # The line above creates:
20
+ #
21
+ # 1) An accessor called Kingsman.stretches, which value is used by default;
22
+ #
23
+ # 2) Some class methods for your model Model.stretches and Model.stretches=
24
+ # which have higher priority than Kingsman.stretches;
25
+ #
26
+ # 3) And an instance method stretches.
27
+ #
28
+ # To add the class methods you need to have a module ClassMethods defined
29
+ # inside the given class.
30
+ #
31
+ def self.config(mod, *accessors) #:nodoc:
32
+ class << mod; attr_accessor :available_configs; end
33
+ mod.available_configs = accessors
34
+
35
+ accessors.each do |accessor|
36
+ mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
37
+ def #{accessor}
38
+ if defined?(@#{accessor})
39
+ @#{accessor}
40
+ elsif superclass.respond_to?(:#{accessor})
41
+ superclass.#{accessor}
42
+ else
43
+ Kingsman.#{accessor}
44
+ end
45
+ end
46
+
47
+ def #{accessor}=(value)
48
+ @#{accessor} = value
49
+ end
50
+ METHOD
51
+ end
52
+ end
53
+
54
+ def self.check_fields!(klass)
55
+ failed_attributes = []
56
+ instance = klass.new
57
+
58
+ klass.kingsman_modules.each do |mod|
59
+ constant = const_get(mod.to_s.classify)
60
+
61
+ constant.required_fields(klass).each do |field|
62
+ failed_attributes << field unless instance.respond_to?(field)
63
+ end
64
+ end
65
+
66
+ if failed_attributes.any?
67
+ fail Kingsman::Models::MissingAttribute.new(failed_attributes)
68
+ end
69
+ end
70
+
71
+ # Include the chosen kingsman modules in your model:
72
+ #
73
+ # kingsman :database_authenticatable, :confirmable, :recoverable
74
+ #
75
+ # You can also give any of the kingsman configuration values in form of a hash,
76
+ # with specific values for this model. Please check your Kingsman initializer
77
+ # for a complete description on those values.
78
+ #
79
+ def kingsman(*modules)
80
+ options = modules.extract_options!.dup
81
+
82
+ selected_modules = modules.map(&:to_sym).uniq.sort_by do |s|
83
+ Kingsman::ALL.index(s) || -1 # follow Kingsman::ALL order
84
+ end
85
+
86
+ kingsman_modules_hook! do
87
+ include Kingsman::Orm
88
+ include Kingsman::Models::Authenticatable
89
+
90
+ selected_modules.each do |m|
91
+ mod = Kingsman::Models.const_get(m.to_s.classify)
92
+
93
+ if mod.const_defined?("ClassMethods")
94
+ class_mod = mod.const_get("ClassMethods")
95
+ extend class_mod
96
+
97
+ if class_mod.respond_to?(:available_configs)
98
+ available_configs = class_mod.available_configs
99
+ available_configs.each do |config|
100
+ next unless options.key?(config)
101
+ send(:"#{config}=", options.delete(config))
102
+ end
103
+ end
104
+ end
105
+
106
+ include mod
107
+ end
108
+
109
+ self.kingsman_modules |= selected_modules
110
+ options.each { |key, value| send(:"#{key}=", value) }
111
+ end
112
+ end
113
+
114
+ # The hook which is called inside kingsman.
115
+ # So your ORM can include kingsman compatibility stuff.
116
+ def kingsman_modules_hook!
117
+ yield
118
+ end
119
+ end
120
+ end
121
+
122
+ require 'kingsman/models/authenticatable'
@@ -0,0 +1,33 @@
1
+ require 'active_support/core_ext/object/with_options'
2
+
3
+ # Kingsman.with_options model: true do |k|
4
+ # k.add_module :database_authenticatable
5
+ # # k.add_module :database_authenticatable, controller: :sessions, route: { session: [nil, :new, :destroy] }
6
+ # end
7
+
8
+ Kingsman.with_options model: true do |k|
9
+ # Strategies first
10
+ k.with_options strategy: true do |s|
11
+ routes = [nil, :new, :destroy]
12
+ s.add_module :database_authenticatable, controller: :sessions, route: { session: routes }
13
+ s.add_module :rememberable, no_input: true
14
+ end
15
+
16
+ # Other authentications
17
+ k.add_module :omniauthable, controller: :omniauth_callbacks, route: :omniauth_callback
18
+
19
+ # Misc after
20
+ routes = [nil, :new, :edit]
21
+ k.add_module :recoverable, controller: :passwords, route: { password: routes }
22
+ k.add_module :registerable, controller: :registrations, route: { registration: (routes << :cancel) }
23
+ k.add_module :validatable
24
+
25
+ # The ones which can sign out after
26
+ routes = [nil, :new]
27
+ k.add_module :confirmable, controller: :confirmations, route: { confirmation: routes }
28
+ k.add_module :lockable, controller: :unlocks, route: { unlock: routes }
29
+ k.add_module :timeoutable
30
+
31
+ # Stats for last, so we make sure the user is really signed in
32
+ k.add_module :trackable
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module OmniAuth
5
+ class StrategyNotFound < NameError
6
+ def initialize(strategy)
7
+ @strategy = strategy
8
+ super("Could not find a strategy with name `#{strategy}'. " \
9
+ "Please ensure it is required or explicitly set it using the :strategy_class option.")
10
+ end
11
+ end
12
+
13
+ class Config
14
+ attr_accessor :strategy
15
+ attr_reader :args, :options, :provider, :strategy_name
16
+
17
+ def initialize(provider, args)
18
+ @provider = provider
19
+ @args = args
20
+ @options = @args.last.is_a?(Hash) ? @args.last : {}
21
+ @strategy = nil
22
+ @strategy_name = options[:name] || @provider
23
+ @strategy_class = options.delete(:strategy_class)
24
+ end
25
+
26
+ def strategy_class
27
+ @strategy_class ||= find_strategy || autoload_strategy
28
+ end
29
+
30
+ def find_strategy
31
+ ::OmniAuth.strategies.find do |strategy_class|
32
+ strategy_class.to_s =~ /#{::OmniAuth::Utils.camelize(strategy_name)}$/ ||
33
+ strategy_class.default_options[:name] == strategy_name
34
+ end
35
+ end
36
+
37
+ def autoload_strategy
38
+ name = ::OmniAuth::Utils.camelize(provider.to_s)
39
+ if ::OmniAuth::Strategies.const_defined?(name)
40
+ ::OmniAuth::Strategies.const_get(name)
41
+ else
42
+ raise StrategyNotFound, name
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ module OmniAuth
5
+ # Note: scope is at the end of the method name for Jets whereas it is at the beginning for Rails
6
+ module UrlHelpers
7
+ def omniauth_authorize_path(resource_or_scope, provider, *args)
8
+ scope = Kingsman::Mapping.find_scope!(resource_or_scope)
9
+ _kingsman_route_context.send("#{provider}_omniauth_authorize_#{scope}_path", *args)
10
+ end
11
+
12
+ def omniauth_authorize_url(resource_or_scope, provider, *args)
13
+ scope = Kingsman::Mapping.find_scope!(resource_or_scope)
14
+ _kingsman_route_context.send("#{provider}_omniauth_authorize_#{scope}_url", *args)
15
+ end
16
+
17
+ def omniauth_callback_path(resource_or_scope, provider, *args)
18
+ scope = Kingsman::Mapping.find_scope!(resource_or_scope)
19
+ _kingsman_route_context.send("#{provider}_omniauth_callback_#{scope}_path", *args)
20
+ end
21
+
22
+ def omniauth_callback_url(resource_or_scope, provider, *args)
23
+ scope = Kingsman::Mapping.find_scope!(resource_or_scope)
24
+ _kingsman_route_context.send("#{provider}_omniauth_callback_#{scope}_url", *args)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem "omniauth", ">= 1.0.0"
5
+
6
+ require "omniauth"
7
+ rescue LoadError
8
+ warn "Could not load 'omniauth'. Please ensure you have the omniauth gem >= 1.0.0 installed and listed in your Gemfile."
9
+ raise
10
+ end
11
+
12
+ # Clean up the default path_prefix. It will be automatically set by Kingsman.
13
+ OmniAuth.config.path_prefix = nil
14
+
15
+ OmniAuth.config.on_failure = Proc.new do |env|
16
+ env['kingsman.mapping'] = Kingsman::Mapping.find_by_path!(env['PATH_INFO'], :path)
17
+ controller_name = ActiveSupport::Inflector.camelize(env['kingsman.mapping'].controllers[:omniauth_callbacks])
18
+ controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
19
+ controller_klass.action(:failure).call(env)
20
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # loaded from config/initalizers/kingsman.rb
4
+ #
5
+ # Kingsman.setup do |config|
6
+ # require 'kingsman/orm/active_record'
7
+ #
8
+ require 'orm_adapter'
9
+ require 'orm_adapter/adapters/active_record'
10
+
11
+ ActiveSupport.on_load(:active_record) do
12
+ extend Kingsman::Models
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:mongoid) do
4
+ require 'orm_adapter/base'
5
+ require 'orm_adapter/adapters/mongoid'
6
+
7
+ Mongoid::Document::ClassMethods.send :include, Kingsman::Models
8
+ end
@@ -0,0 +1,37 @@
1
+ module Kingsman
2
+ module Orm # :nodoc:
3
+ def self.active_record?(model)
4
+ defined?(ActiveRecord) && model < ActiveRecord::Base
5
+ end
6
+
7
+ def self.included(model)
8
+ model.include DirtyTrackingMethods
9
+ end
10
+
11
+ module DirtyTrackingMethods
12
+ def kingsman_email_before_last_save
13
+ email_before_last_save
14
+ end
15
+
16
+ def kingsman_email_in_database
17
+ email_in_database
18
+ end
19
+
20
+ def kingsman_saved_change_to_email?
21
+ saved_change_to_email?
22
+ end
23
+
24
+ def kingsman_saved_change_to_encrypted_password?
25
+ saved_change_to_encrypted_password?
26
+ end
27
+
28
+ def kingsman_will_save_change_to_email?
29
+ will_save_change_to_email?
30
+ end
31
+
32
+ def kingsman_respond_to_and_will_save_change_to_attribute?(attribute)
33
+ respond_to?("will_save_change_to_#{attribute}?") && send("will_save_change_to_#{attribute}?")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ class ParameterFilter
5
+ def initialize(case_insensitive_keys, strip_whitespace_keys)
6
+ @case_insensitive_keys = case_insensitive_keys || []
7
+ @strip_whitespace_keys = strip_whitespace_keys || []
8
+ end
9
+
10
+ def filter(conditions)
11
+ conditions = stringify_params(conditions.dup)
12
+
13
+ conditions.merge!(filtered_hash_by_method_for_given_keys(conditions.dup, :downcase, @case_insensitive_keys))
14
+ conditions.merge!(filtered_hash_by_method_for_given_keys(conditions.dup, :strip, @strip_whitespace_keys))
15
+
16
+ conditions
17
+ end
18
+
19
+ def filtered_hash_by_method_for_given_keys(conditions, method, condition_keys)
20
+ condition_keys.each do |k|
21
+ next unless conditions.key?(k)
22
+
23
+ value = conditions[k]
24
+ conditions[k] = value.send(method) if value.respond_to?(method)
25
+ end
26
+
27
+ conditions
28
+ end
29
+
30
+ # Force keys to be string to avoid injection on mongoid related database.
31
+ def stringify_params(conditions)
32
+ return conditions unless conditions.is_a?(Hash)
33
+ conditions.each do |k, v|
34
+ conditions[k] = v.to_s if param_requires_string_conversion?(v)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def param_requires_string_conversion?(value)
41
+ true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ # The +ParameterSanitizer+ deals with permitting specific parameters values
5
+ # for each +Kingsman+ scope in the application.
6
+ #
7
+ # The sanitizer knows about Kingsman default parameters (like +password+ and
8
+ # +password_confirmation+ for the `RegistrationsController`), and you can
9
+ # extend or change the permitted parameters list on your controllers.
10
+ #
11
+ # === Permitting new parameters
12
+ #
13
+ # You can add new parameters to the permitted list using the +permit+ method
14
+ # in a +before_action+ method, for instance.
15
+ #
16
+ # class ApplicationController < ActionController::Base
17
+ # before_action :configure_permitted_parameters, if: :kingsman_controller?
18
+ #
19
+ # protected
20
+ #
21
+ # def configure_permitted_parameters
22
+ # # Permit the `subscribe_newsletter` parameter along with the other
23
+ # # sign up parameters.
24
+ # kingsman_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
25
+ # end
26
+ # end
27
+ #
28
+ # Using a block yields an +ActionController::Parameters+ object so you can
29
+ # permit nested parameters and have more control over how the parameters are
30
+ # permitted in your controller.
31
+ #
32
+ # def configure_permitted_parameters
33
+ # kingsman_parameter_sanitizer.permit(:sign_up) do |user|
34
+ # user.permit(newsletter_preferences: [])
35
+ # end
36
+ # end
37
+ class ParameterSanitizer
38
+ DEFAULT_PERMITTED_ATTRIBUTES = {
39
+ sign_in: [:password, :remember_me],
40
+ sign_up: [:password, :password_confirmation],
41
+ account_update: [:password, :password_confirmation, :current_password]
42
+ }
43
+
44
+ def initialize(resource_class, resource_name, params)
45
+ @auth_keys = extract_auth_keys(resource_class)
46
+ @params = params
47
+ @resource_name = resource_name
48
+ @permitted = {}
49
+
50
+ DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
51
+ permit(action, keys: keys)
52
+ end
53
+ end
54
+
55
+ # Sanitize the parameters for a specific +action+.
56
+ #
57
+ # === Arguments
58
+ #
59
+ # * +action+ - A +Symbol+ with the action that the controller is
60
+ # performing, like +sign_up+, +sign_in+, etc.
61
+ #
62
+ # === Examples
63
+ #
64
+ # # Inside the `RegistrationsController#create` action.
65
+ # resource = build_resource(kingsman_parameter_sanitizer.sanitize(:sign_up))
66
+ # resource.save
67
+ #
68
+ # Returns an +ActiveSupport::HashWithIndifferentAccess+ with the permitted
69
+ # attributes.
70
+ def sanitize(action)
71
+ permissions = @permitted[action]
72
+
73
+ if permissions.respond_to?(:call)
74
+ cast_to_hash permissions.call(default_params)
75
+ elsif permissions.present?
76
+ cast_to_hash permit_keys(default_params, permissions)
77
+ else
78
+ unknown_action!(action)
79
+ end
80
+ end
81
+
82
+ # Add or remove new parameters to the permitted list of an +action+.
83
+ #
84
+ # === Arguments
85
+ #
86
+ # * +action+ - A +Symbol+ with the action that the controller is
87
+ # performing, like +sign_up+, +sign_in+, etc.
88
+ # * +keys:+ - An +Array+ of keys that also should be permitted.
89
+ # * +except:+ - An +Array+ of keys that shouldn't be permitted.
90
+ # * +block+ - A block that should be used to permit the action
91
+ # parameters instead of the +Array+ based approach. The block will be
92
+ # called with an +ActionController::Parameters+ instance.
93
+ #
94
+ # === Examples
95
+ #
96
+ # # Adding new parameters to be permitted in the `sign_up` action.
97
+ # kingsman_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
98
+ #
99
+ # # Removing the `password` parameter from the `account_update` action.
100
+ # kingsman_parameter_sanitizer.permit(:account_update, except: [:password])
101
+ #
102
+ # # Using the block form to completely override how we permit the
103
+ # # parameters for the `sign_up` action.
104
+ # kingsman_parameter_sanitizer.permit(:sign_up) do |user|
105
+ # user.permit(:email, :password, :password_confirmation)
106
+ # end
107
+ #
108
+ #
109
+ # Returns nothing.
110
+ def permit(action, keys: nil, except: nil, &block)
111
+ if block_given?
112
+ @permitted[action] = block
113
+ end
114
+
115
+ if keys.present?
116
+ @permitted[action] ||= @auth_keys.dup
117
+ @permitted[action].concat(keys)
118
+ end
119
+
120
+ if except.present?
121
+ @permitted[action] ||= @auth_keys.dup
122
+ @permitted[action] = @permitted[action] - except
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ # Cast a sanitized +ActionController::Parameters+ to a +HashWithIndifferentAccess+
129
+ # that can be used elsewhere.
130
+ #
131
+ # Returns an +ActiveSupport::HashWithIndifferentAccess+.
132
+ def cast_to_hash(params)
133
+ # TODO: Remove the `with_indifferent_access` method call when we only support Rails 5+.
134
+ params && params.to_h.with_indifferent_access
135
+ end
136
+
137
+ def default_params
138
+ if hashable_resource_params?
139
+ @params.fetch(@resource_name)
140
+ else
141
+ empty_params
142
+ end
143
+ end
144
+
145
+ def hashable_resource_params?
146
+ @params[@resource_name].respond_to?(:permit)
147
+ end
148
+
149
+ def empty_params
150
+ ActionController::Parameters.new({})
151
+ end
152
+
153
+ def permit_keys(parameters, keys)
154
+ parameters.permit(*keys)
155
+ end
156
+
157
+ def extract_auth_keys(klass)
158
+ auth_keys = klass.authentication_keys
159
+
160
+ auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys
161
+ end
162
+
163
+ def unknown_action!(action)
164
+ raise NotImplementedError, <<-MESSAGE.strip_heredoc
165
+ "Kingsman doesn't know how to sanitize parameters for '#{action}'".
166
+ If you want to define a new set of parameters to be sanitized use the
167
+ `permit` method first:
168
+
169
+ kingsman_parameter_sanitizer.permit(:#{action}, keys: [:param1, :param2, :param3])
170
+ MESSAGE
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kingsman
4
+ class SecretKeyFinder
5
+ def initialize(application)
6
+ @application = application
7
+ end
8
+
9
+ def find
10
+ if @application.respond_to?(:credentials) && key_exists?(@application.credentials)
11
+ @application.credentials.secret_key_base
12
+ elsif @application.respond_to?(:secrets) && key_exists?(@application.secrets)
13
+ @application.secrets.secret_key_base
14
+ elsif @application.config.respond_to?(:secret_key_base) && key_exists?(@application.config)
15
+ @application.config.secret_key_base
16
+ elsif @application.respond_to?(:secret_key_base) && key_exists?(@application)
17
+ @application.secret_key_base
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def key_exists?(object)
24
+ object.secret_key_base.present?
25
+ end
26
+ end
27
+ end