Class: Heimdallr::Proxy::Record
- Inherits:
-
Object
- Object
- Heimdallr::Proxy::Record
- Defined in:
- lib/heimdallr/proxy/record.rb
Overview
A security-aware proxy for individual records. This class validates all the method calls and either forwards them to the encapsulated object or raises an exception.
The #touch method call isn't considered a security threat and as such, it is forwarded to the underlying object directly.
Record proxies can be of two types, implicit and explicit. Implicit proxies return nil on access to methods forbidden by the current security context; explicit proxies raise an Heimdallr::PermissionError instead.
Instance Method Summary (collapse)
-
- (Object) assign_attributes
Delegates to the corresponding method of underlying object.
-
- (Object) attributes
A proxy for attributes method which removes all attributes without :view permission.
-
- (Object) check_attributes
protected
Raises an exception if any of the changed attributes are not valid for the current security context.
-
- (Object) check_save_options(options)
protected
Raises an exception if any of the options intended for use in save methods are potentially unsafe.
-
- (String) class_name
Class name of the underlying model.
-
- (Object) decrement(field, by = 1)
Delegates to the corresponding method of underlying object.
-
- (Object) errors
Delegates to the corresponding method of underlying object.
-
- (Heimdallr::Proxy::Record) explicit
Return an explicit variant of this proxy.
-
- (Heimdallr::Proxy::Record) implicit
Return an implicit variant of this proxy.
-
- (Object) increment(field, by = 1)
Delegates to the corresponding method of underlying object.
-
- (Record) initialize(context, record, options = {})
constructor
Create a record proxy.
-
- (ActiveRecord::Base) insecure
Return the underlying object.
-
- (String) inspect
Describes the proxy and proxified object.
-
- (Object) invalid?
Delegates to the corresponding method of underlying object.
-
- (Object) method_missing(method, *args, &block)
A whitelisting dispatcher for attribute-related method calls.
-
- (Hash) reflect_on_security
Return the associated security metadata.
-
- (Object) restrict(context)
Records cannot be restricted twice.
-
- (Object) save(options = {})
A proxy for save method which verifies all of the dirty attributes to be valid for current security context.
-
- (Object) save!(options = {})
A proxy for save method which verifies all of the dirty attributes to be valid for current security context and mandates the current record to be valid.
-
- (Object) toggle(field)
Delegates to the corresponding method of underlying object.
-
- (Object) touch(field)
Delegates to the corresponding method of underlying object.
-
- (Object) update_attributes(attributes, options = {})
A proxy for update_attributes method.
-
- (Object) update_attributes!(attributes, options = {})
A proxy for update_attributes! method.
-
- (Object) valid?
Delegates to the corresponding method of underlying object.
Constructor Details
- (Record) initialize(context, record, options = {})
Create a record proxy.
18 19 20 21 22 |
# File 'lib/heimdallr/proxy/record.rb', line 18 def initialize(context, record, ={}) @context, @record, @options = context, record, @restrictions = @record.class.restrictions(context) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
- (Object) method_missing(method, *args, &block)
A whitelisting dispatcher for attribute-related method calls. Every unknown method is first normalized (that is, stripped of its ? or = suffix). Then, if the normalized form is whitelisted, it is passed to the underlying object as-is. Otherwise, an exception is raised.
If the underlying object is an instance of ActiveRecord, then all association accesses are resolved and proxified automatically.
Note that only the attribute and collection getters and setters are dispatched through this method. Every other model method should be defined as an instance method of this class in order to work.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/heimdallr/proxy/record.rb', line 159 def method_missing(method, *args, &block) suffix = method.to_s[-1] if %w(? = !).include? suffix normalized_method = method[0..-2].to_sym else normalized_method = method suffix = nil end if (defined?(ActiveRecord) && @record.is_a?(ActiveRecord::Reflection) && association = @record.class.reflect_on_association(method)) || (!@record.class.heimdallr_relations.nil? && @record.class.heimdallr_relations.include?(normalized_method)) referenced = @record.send(method, *args) if referenced.nil? nil elsif referenced.respond_to? :restrict referenced.restrict(@context, @options) elsif Heimdallr.allow_insecure_associations referenced else raise Heimdallr::InsecureOperationError, "Attempt to fetch insecure association #{method}. Try #insecure" end elsif @record.respond_to? method if [nil, '?'].include?(suffix) if @restrictions.allowed_fields[:view].include?(normalized_method) @record.send method, *args, &block elsif @options[:implicit] nil else raise Heimdallr::PermissionError, "Attempt to fetch non-whitelisted attribute #{method}" end elsif suffix == '=' @record.send method, *args else raise Heimdallr::PermissionError, "Non-whitelisted method #{method} is called for #{@record.inspect} " end else super end end |
Instance Method Details
- (Object) assign_attributes
Delegates to the corresponding method of underlying object.
129 |
# File 'lib/heimdallr/proxy/record.rb', line 129 delegate :assign_attributes, :to => :@record |
- (Object) attributes
A proxy for attributes method which removes all attributes without :view permission.
45 46 47 48 49 50 51 52 53 |
# File 'lib/heimdallr/proxy/record.rb', line 45 def attributes @record.attributes.tap do |attributes| attributes.keys.each do |key| unless @restrictions.allowed_fields[:view].include? key.to_sym attributes[key] = nil end end end end |
- (Object) check_attributes (protected)
Raises an exception if any of the changed attributes are not valid for the current security context.
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/heimdallr/proxy/record.rb', line 255 def check_attributes @record.errors.clear if @record.new_record? action = :create else action = :update end allowed_fields = @restrictions.allowed_fields[action] fixtures = @restrictions.fixtures[action] validators = @restrictions.validators[action] @record.changed.map(&:to_sym).each do |attribute| value = @record.send attribute if fixtures.has_key? attribute if fixtures[attribute] != value raise Heimdallr::PermissionError, "Attribute #{attribute} value (#{value}) is not equal to a fixture (#{fixtures[attribute]})" end elsif !allowed_fields.include? attribute raise Heimdallr::PermissionError, "Attribute #{attribute} is not allowed to change" end end @record.heimdallr_validators = validators yield ensure @record.heimdallr_validators = nil end |
- (Object) check_save_options(options) (protected)
Raises an exception if any of the options intended for use in save methods are potentially unsafe.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/heimdallr/proxy/record.rb', line 291 def () if [:validate] == false raise Heimdallr::InsecureOperationError, "Saving while omitting validation would omit security validations too" end if @record.new_record? unless @restrictions.can? :create raise Heimdallr::InsecureOperationError, "Creating was not explicitly allowed" end else unless @restrictions.can? :update raise Heimdallr::InsecureOperationError, "Updating was not explicitly allowed" end end end |
- (String) class_name
Class name of the underlying model.
133 134 135 |
# File 'lib/heimdallr/proxy/record.rb', line 133 def class_name @record.class.name end |
- (Object) decrement(field, by = 1)
Delegates to the corresponding method of underlying object.
27 |
# File 'lib/heimdallr/proxy/record.rb', line 27 delegate :decrement, :to => :@record |
- (Object) errors
Delegates to the corresponding method of underlying object.
125 |
# File 'lib/heimdallr/proxy/record.rb', line 125 delegate :errors, :to => :@record |
- (Heimdallr::Proxy::Record) explicit
Return an explicit variant of this proxy.
221 222 223 |
# File 'lib/heimdallr/proxy/record.rb', line 221 def explicit Proxy::Record.new(@context, @record, @options.merge(implicit: false)) end |
- (Heimdallr::Proxy::Record) implicit
Return an implicit variant of this proxy.
214 215 216 |
# File 'lib/heimdallr/proxy/record.rb', line 214 def implicit Proxy::Record.new(@context, @record, @options.merge(implicit: true)) end |
- (Object) increment(field, by = 1)
Delegates to the corresponding method of underlying object.
31 |
# File 'lib/heimdallr/proxy/record.rb', line 31 delegate :increment, :to => :@record |
- (ActiveRecord::Base) insecure
Return the underlying object.
207 208 209 |
# File 'lib/heimdallr/proxy/record.rb', line 207 def insecure @record end |
- (String) inspect
Describes the proxy and proxified object.
228 229 230 |
# File 'lib/heimdallr/proxy/record.rb', line 228 def inspect "#<Heimdallr::Proxy::Record: #{@record.inspect}>" end |
- (Object) invalid?
Delegates to the corresponding method of underlying object.
121 |
# File 'lib/heimdallr/proxy/record.rb', line 121 delegate :invalid?, :to => :@record |
- (Hash) reflect_on_security
Return the associated security metadata. The returned hash will contain keys :context, :record, :options, corresponding to the parameters in #initialize, and :model, representing the model class.
Such a name was deliberately selected for this method in order to reduce namespace pollution.
240 241 242 243 244 245 246 247 |
# File 'lib/heimdallr/proxy/record.rb', line 240 def reflect_on_security { model: @record.class, context: @context, record: @record, options: @options }.merge(@restrictions.reflection) end |
- (Object) restrict(context)
Records cannot be restricted twice.
140 141 142 |
# File 'lib/heimdallr/proxy/record.rb', line 140 def restrict(context) raise RuntimeError, "Records cannot be restricted twice" end |
- (Object) save(options = {})
A proxy for save method which verifies all of the dirty attributes to be valid for current security context.
81 82 83 84 85 86 87 |
# File 'lib/heimdallr/proxy/record.rb', line 81 def save(={}) check_attributes do @record.save() end end |
- (Object) save!(options = {})
A proxy for save method which verifies all of the dirty attributes to be valid for current security context and mandates the current record to be valid.
96 97 98 99 100 101 102 |
# File 'lib/heimdallr/proxy/record.rb', line 96 def save!(={}) check_attributes do @record.save!() end end |
- (Object) toggle(field)
Delegates to the corresponding method of underlying object.
35 |
# File 'lib/heimdallr/proxy/record.rb', line 35 delegate :toggle, :to => :@record |
- (Object) touch(field)
Delegates to the corresponding method of underlying object. This method does not modify any fields except for the timestamp itself and thus is not considered as a potential security threat.
41 |
# File 'lib/heimdallr/proxy/record.rb', line 41 delegate :touch, :to => :@record |
- (Object) update_attributes(attributes, options = {})
A proxy for update_attributes method. See also #save.
59 60 61 62 63 64 |
# File 'lib/heimdallr/proxy/record.rb', line 59 def update_attributes(attributes, ={}) @record.with_transaction_returning_status do @record.assign_attributes(attributes, ) save end end |
- (Object) update_attributes!(attributes, options = {})
A proxy for update_attributes! method. See also #save!.
70 71 72 73 74 75 |
# File 'lib/heimdallr/proxy/record.rb', line 70 def update_attributes!(attributes, ={}) @record.with_transaction_returning_status do @record.assign_attributes(attributes, ) save! end end |
- (Object) valid?
Delegates to the corresponding method of underlying object.
117 |
# File 'lib/heimdallr/proxy/record.rb', line 117 delegate :valid?, :to => :@record |