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, &regulation) regulate_dispatches_from(*args) { true } end def dispatch_to(*args, &regulation) - 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, &regulation) end CHANGE_POLICIES = [:create, :update, :destroy] @@ -80,18 +96,44 @@ model.class_eval { define_method("#{policy}_permitted?", &regulation) } 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, &regulation) 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, &regulation end end def process_args(policy, allowed_opts, args, regulation) @@ -199,12 +241,27 @@ end class ClassConnectionRegulation < Regulation def self.add_regulation(klass, opts={}, &regulation) - 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|