lib/hyper-operation/transport/policy.rb in hyper-operation-0.5.12 vs lib/hyper-operation/transport/policy.rb in hyper-operation-0.99.0
- old
+ new
@@ -1,10 +1,20 @@
module Hyperloop
class InternalClassPolicy
def initialize(regulated_klass)
+ unless regulated_klass.is_a?(Class)
+ # attempt to constantize the class in case eager_loading in production
+ # has loaded the policy before the class. THis will insure that if
+ # there is a class being regulated, it is loaded first.
+ begin
+ regulated_klass.constantize
+ rescue NameError
+ nil
+ end
+ end
@regulated_klass = regulated_klass
end
attr_reader :regulated_klass
@@ -47,15 +57,21 @@
def always_dispatch_from(*args, ®ulation)
regulate_dispatches_from(*args) { true }
end
def dispatch_to(*args, ®ulation)
- actual_klass = regulated_klass.is_a?(Class) ? regulated_klass : regulated_klass.constantize rescue nil
- actual_klass.dispatch_to(actual_klass) if actual_klass.respond_to? :dispatch_to
- unless actual_klass.respond_to? :dispatch_to
- raise 'you can only dispatch_to Operation classes'
- end
+ actual_klass = if regulated_klass.is_a?(Class)
+ regulated_klass
+ else
+ begin
+ regulated_klass.constantize
+ rescue NameError
+ nil
+ end
+ end
+ raise 'you can only dispatch_to Operation classes' unless actual_klass.respond_to? :dispatch_to
+ actual_klass.dispatch_to(actual_klass)
actual_klass.dispatch_to(*args, ®ulation)
end
CHANGE_POLICIES = [:create, :update, :destroy]
@@ -80,18 +96,44 @@
model.class_eval { define_method("#{policy}_permitted?", ®ulation) }
end
end
end
+ def self.ar_base_descendants_map_cache
+ @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
+ end
+
def get_ar_model(str)
- str.is_a?(Class) ? str : Object.const_get(str)
- rescue
- raise "#{str} is not a class"
+ if str.is_a?(Class)
+ unless str <= ActiveRecord::Base
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is not a subclass of ActiveRecord::Base")
+ end
+ str
+ else
+ # we used to cache this here, but during eager loading the cache may get partially filled and never updated
+ # so this guard will fail, now performance will be suckish, as this guard, required for security, takes some ms
+ # def self.ar_base_descendants_map_cache
+ # @ar_base_descendants_map_cache ||= ActiveRecord::Base.descendants.map(&:name)
+ # end
+ # if Rails.env.production? && !Hyperloop::InternalClassPolicy.ar_base_descendants_map_cache.include?(str)
+ if Rails.application.config.eager_load && !ActiveRecord::Base.descendants.map(&:name).include?(str)
+ # AR::Base.descendants is eager loaded in production -> this guard works.
+ # In development it may be empty or partially filled -> this guard may fail.
+ # Thus guarded here only in production.
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:non_ar_class, "#{str} is either not defined or is not a subclass of ActiveRecord::Base")
+ end
+ Object.const_get(str)
+ end
end
+ def self.regulated_klasses
+ @regulated_klasses ||= Set.new
+ end
+
def regulate(regulation_klass, policy, args, ®ulation)
process_args(policy, regulation_klass.allowed_opts, args, regulation) do |regulated_klass, opts|
+ self.class.regulated_klasses << regulated_klass.to_s
regulation_klass.add_regulation regulated_klass, opts, ®ulation
end
end
def process_args(policy, allowed_opts, args, regulation)
@@ -199,12 +241,27 @@
end
class ClassConnectionRegulation < Regulation
def self.add_regulation(klass, opts={}, ®ulation)
- actual_klass = klass.is_a?(Class) ? klass : klass.constantize rescue nil
- actual_klass.dispatch_to(actual_klass) if actual_klass.respond_to? :dispatch_to rescue nil
+ actual_klass = if klass.is_a?(Class)
+ klass
+ else
+ begin
+ klass.constantize
+ rescue NameError
+ nil
+ end
+ end
+ if actual_klass && actual_klass.respond_to?(:dispatch_to)
+ begin
+ actual_klass.dispatch_to(actual_klass)
+ rescue NoMethodError
+ # this is the case for ClassPolicy where the instance method :dispatch_to has been deleted.
+ nil
+ end
+ end
super
end
def connectable?(acting_user)
regulate_for(acting_user).all? unless regulations.empty? rescue nil
@@ -319,14 +376,22 @@
def obj
@obj
end
+ def self.raise_operation_access_violation(message, details)
+ Hyperloop.on_error(Hyperloop::AccessViolation, message, details)
+ raise Hyperloop::AccessViolation
+ end
+
def self.regulate_connection(acting_user, channel_string)
channel = channel_string.split("-")
if channel.length > 1
id = channel[1..-1].join("-")
+ unless Hyperloop::InternalClassPolicy.regulated_klasses.include?(channel[0])
+ Hyperloop::InternalPolicy.raise_operation_access_violation(:not_a_channel, "#{channel[0]} is not regulated channel class")
+ end
object = Object.const_get(channel[0]).find(id)
InstanceConnectionRegulation.connect(object, acting_user)
else
ClassConnectionRegulation.connect(channel[0], acting_user)
end
@@ -484,11 +549,11 @@
end unless instance_methods(false).include?(:initialize)
end
module PolicyAutoLoader
def self.load(name, value)
- const_get("#{name}Policy") if name && !(name =~ /Policy$/) && value.is_a?(Class)
+ const_get("#{name}Policy") if name && !name.end_with?("Policy".freeze) && value.is_a?(Class)
rescue Exception => e
raise e if e.is_a?(LoadError) && e.message =~ /Unable to autoload constant #{name}Policy/
end
end
end
@@ -513,11 +578,11 @@
end
end
Hyperloop::ClassPolicyMethods.instance_methods.each do |method|
define_method method do |*args, &block|
- if name =~ /Policy$/
- @hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.gsub(/Policy$/,""))
+ if name.end_with?("Policy".freeze)
+ @hyperloop_internal_policy_object = Hyperloop::InternalClassPolicy.new(name.sub(/Policy$/,""))
include Hyperloop::PolicyMethods
send method, *args, &block
else
class << self
Hyperloop::ClassPolicyMethods.instance_methods.each do |method|