File: parameter_sanitizer.rb

package info (click to toggle)
ruby-devise 4.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 752 kB
  • sloc: ruby: 3,877; sh: 24; makefile: 11
file content (173 lines) | stat: -rw-r--r-- 5,724 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# frozen_string_literal: true

module Devise
  # The +ParameterSanitizer+ deals with permitting specific parameters values
  # for each +Devise+ scope in the application.
  #
  # The sanitizer knows about Devise default parameters (like +password+ and
  # +password_confirmation+ for the `RegistrationsController`), and you can
  # extend or change the permitted parameters list on your controllers.
  #
  # === Permitting new parameters
  #
  # You can add new parameters to the permitted list using the +permit+ method
  # in a +before_action+ method, for instance.
  #
  #    class ApplicationController < ActionController::Base
  #      before_action :configure_permitted_parameters, if: :devise_controller?
  #
  #      protected
  #
  #      def configure_permitted_parameters
  #        # Permit the `subscribe_newsletter` parameter along with the other
  #        # sign up parameters.
  #        devise_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
  #      end
  #    end
  #
  # Using a block yields an +ActionController::Parameters+ object so you can
  # permit nested parameters and have more control over how the parameters are
  # permitted in your controller.
  #
  #    def configure_permitted_parameters
  #      devise_parameter_sanitizer.permit(:sign_up) do |user|
  #        user.permit(newsletter_preferences: [])
  #      end
  #    end
  class ParameterSanitizer
    DEFAULT_PERMITTED_ATTRIBUTES = {
      sign_in: [:password, :remember_me],
      sign_up: [:password, :password_confirmation],
      account_update: [:password, :password_confirmation, :current_password]
    }

    def initialize(resource_class, resource_name, params)
      @auth_keys      = extract_auth_keys(resource_class)
      @params         = params
      @resource_name  = resource_name
      @permitted      = {}

      DEFAULT_PERMITTED_ATTRIBUTES.each_pair do |action, keys|
        permit(action, keys: keys)
      end
    end

    # Sanitize the parameters for a specific +action+.
    #
    # === Arguments
    #
    # * +action+ - A +Symbol+ with the action that the controller is
    #   performing, like +sign_up+, +sign_in+, etc.
    #
    # === Examples
    #
    #    # Inside the `RegistrationsController#create` action.
    #    resource = build_resource(devise_parameter_sanitizer.sanitize(:sign_up))
    #    resource.save
    #
    # Returns an +ActiveSupport::HashWithIndifferentAccess+ with the permitted
    # attributes.
    def sanitize(action)
      permissions = @permitted[action]

      if permissions.respond_to?(:call)
        cast_to_hash permissions.call(default_params)
      elsif permissions.present?
        cast_to_hash permit_keys(default_params, permissions)
      else
        unknown_action!(action)
      end
    end

    # Add or remove new parameters to the permitted list of an +action+.
    #
    # === Arguments
    #
    # * +action+ - A +Symbol+ with the action that the controller is
    #   performing, like +sign_up+, +sign_in+, etc.
    # * +keys:+     - An +Array+ of keys that also should be permitted.
    # * +except:+   - An +Array+ of keys that shouldn't be permitted.
    # * +block+     - A block that should be used to permit the action
    #   parameters instead of the +Array+ based approach. The block will be
    #   called with an +ActionController::Parameters+ instance.
    #
    # === Examples
    #
    #   # Adding new parameters to be permitted in the `sign_up` action.
    #   devise_parameter_sanitizer.permit(:sign_up, keys: [:subscribe_newsletter])
    #
    #   # Removing the `password` parameter from the `account_update` action.
    #   devise_parameter_sanitizer.permit(:account_update, except: [:password])
    #
    #   # Using the block form to completely override how we permit the
    #   # parameters for the `sign_up` action.
    #   devise_parameter_sanitizer.permit(:sign_up) do |user|
    #     user.permit(:email, :password, :password_confirmation)
    #   end
    #
    #
    # Returns nothing.
    def permit(action, keys: nil, except: nil, &block)
      if block_given?
        @permitted[action] = block
      end

      if keys.present?
        @permitted[action] ||= @auth_keys.dup
        @permitted[action].concat(keys)
      end

      if except.present?
        @permitted[action] ||= @auth_keys.dup
        @permitted[action] = @permitted[action] - except
      end
    end

    private

    # Cast a sanitized +ActionController::Parameters+ to a +HashWithIndifferentAccess+
    # that can be used elsewhere.
    #
    # Returns an +ActiveSupport::HashWithIndifferentAccess+.
    def cast_to_hash(params)
      # TODO: Remove the `with_indifferent_access` method call when we only support Rails 5+.
      params && params.to_h.with_indifferent_access
    end

    def default_params
      if hashable_resource_params?
        @params.fetch(@resource_name)
      else
        empty_params
      end
    end

    def hashable_resource_params?
      @params[@resource_name].respond_to?(:permit)
    end

    def empty_params
      ActionController::Parameters.new({})
    end

    def permit_keys(parameters, keys)
      parameters.permit(*keys)
    end

    def extract_auth_keys(klass)
      auth_keys = klass.authentication_keys

      auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys
    end

    def unknown_action!(action)
      raise NotImplementedError, <<-MESSAGE.strip_heredoc
        "Devise doesn't know how to sanitize parameters for '#{action}'".
        If you want to define a new set of parameters to be sanitized use the
        `permit` method first:

          devise_parameter_sanitizer.permit(:#{action}, keys: [:param1, :param2, :param3])
      MESSAGE
    end
  end
end