require 'spec_helpers/boot' if defined?(ActiveRecord) load 'spec_helpers/adapters/active_record.rb' describe Protector::Adapters::ActiveRecord do before(:all) do load 'migrations/active_record.rb' module ProtectionCase extend ActiveSupport::Concern included do |klass| protect do |x| if x == '-' scope{ where('1=0') } elsif x == '+' scope{ where(klass.table_name => {number: 999}) } end can :read, :dummy_id unless x == '-' end end end [Dummy, Fluffy].each{|c| c.send :include, ProtectionCase} Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext' Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext' Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext' Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext' [Fluffy, Bobby].each do |m| m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 1 m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 1 m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 2 m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 2 end Fluffy.all.each{|f| Loony.create! fluffy_id: f.id, string: 'zomgstring' } end let(:dummy) do Class.new(ActiveRecord::Base) do def self.name; 'Dummy'; end def self.model_name; ActiveModel::Name.new(self, nil, "dummy"); end self.table_name = "dummies" scope :none, where('1 = 0') unless respond_to?(:none) end end describe Protector::Adapters::ActiveRecord do it "finds out whether object is AR relation" do Protector::Adapters::ActiveRecord.is?(Dummy).should == true Protector::Adapters::ActiveRecord.is?(Dummy.every).should == true end it "sets the adapter" do Dummy.restrict!('!').protector_meta.adapter.should == Protector::Adapters::ActiveRecord end end # # Model instance # describe Protector::Adapters::ActiveRecord::Base do it "includes" do Dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base) end it "scopes" do scope = Dummy.restrict!('!') scope.should be_a_kind_of ActiveRecord::Relation scope.protector_subject.should == '!' end it_behaves_like "a model" it "validates on create" do dummy.instance_eval do protect do; end end instance = dummy.restrict!('!').create(string: 'test') instance.errors[:base].should == ["Access denied to 'string'"] instance.delete end it "validates on create!" do dummy.instance_eval do protect do; end end expect { dummy.restrict!('!').create!(string: 'test').delete }.to raise_error end it "validates on new{}" do dummy.instance_eval do protect do; end end result = dummy.restrict!('!').new do |instance| instance.protector_subject.should == '!' end result.protector_subject.should == '!' end it "finds with scope on id column" do dummy.instance_eval do protect do scope { where(id: 1) } end end expect { dummy.restrict!('!').find(1) }.to_not raise_error expect { dummy.restrict!('!').find(2) }.to raise_error end it "allows for validations" do dummy.instance_eval do validates :string, presence: true protect do; can :create; end end instance = dummy.restrict!('!').new(string: 'test') instance.save.should == true instance.delete end end # # Model scope # describe Protector::Adapters::ActiveRecord::Relation do it "includes" do Dummy.none.ancestors.should include(Protector::Adapters::ActiveRecord::Base) end it "saves subject" do Dummy.restrict!('!').where(number: 999).protector_subject.should == '!' Dummy.restrict!('!').except(:order).protector_subject.should == '!' Dummy.restrict!('!').only(:order).protector_subject.should == '!' end it "forwards subject" do Dummy.restrict!('!').where(number: 999).first.protector_subject.should == '!' Dummy.restrict!('!').where(number: 999).to_a.first.protector_subject.should == '!' Dummy.restrict!('!').new.protector_subject.should == '!' Dummy.restrict!('!').first.fluffies.new.protector_subject.should == '!' Dummy.first.fluffies.restrict!('!').new.protector_subject.should == '!' end it "checks creatability" do Dummy.restrict!('!').creatable?.should == false Dummy.restrict!('!').where(number: 999).creatable?.should == false end context "with open relation" do context "adequate", paranoid: false do it "checks existence" do Dummy.any?.should == true Dummy.restrict!('!').any?.should == true end it "counts" do Dummy.count.should == 4 dummy = Dummy.restrict!('!') dummy.count.should == 4 dummy.protector_subject?.should == true end it "fetches" do fetched = Dummy.restrict!('!').to_a Dummy.count.should == 4 fetched.length.should == 4 end end context "paranoid", paranoid: true do it "checks existence" do Dummy.any?.should == true Dummy.restrict!('!').any?.should == false end it "counts" do Dummy.count.should == 4 dummy = Dummy.restrict!('!') dummy.count.should == 0 dummy.protector_subject?.should == true end it "fetches" do fetched = Dummy.restrict!('!').to_a Dummy.count.should == 4 fetched.length.should == 0 end end end context "with null relation" do it "checks existence" do Dummy.any?.should == true Dummy.restrict!('-').any?.should == false end it "counts" do Dummy.count.should == 4 dummy = Dummy.restrict!('-') dummy.count.should == 0 dummy.protector_subject?.should == true end it "fetches" do fetched = Dummy.restrict!('-').to_a Dummy.count.should == 4 fetched.length.should == 0 end it "keeps security scope when unscoped" do Dummy.unscoped.restrict!('-').count.should == 0 Dummy.restrict!('-').unscoped.count.should == 0 end end context "with active relation" do it "checks existence" do Dummy.any?.should == true Dummy.restrict!('+').any?.should == true end it "counts" do Dummy.count.should == 4 dummy = Dummy.restrict!('+') dummy.count.should == 2 dummy.protector_subject?.should == true end it "fetches" do fetched = Dummy.restrict!('+').to_a Dummy.count.should == 4 fetched.length.should == 2 end it "keeps security scope when unscoped" do Dummy.unscoped.restrict!('+').count.should == 2 Dummy.restrict!('+').unscoped.count.should == 2 end end end # # Model scope # describe Protector::Adapters::ActiveRecord::Association do describe "validates on create! within association" do it "when restricted from entity" do expect { Dummy.first.restrict!('-').fluffies.create!(string: 'test').delete }.to raise_error end it "when restricted from association" do expect { Dummy.first.fluffies.restrict!('-').create!(string: 'test').delete }.to raise_error end end context "singular association" do it "forwards subject" do Fluffy.restrict!('!').first.dummy.protector_subject.should == '!' Fluffy.first.restrict!('!').dummy.protector_subject.should == '!' end it "forwards cached subject" do Dummy.first.fluffies.restrict!('!').first.dummy.protector_subject.should == '!' end end context "collection association" do it "forwards subject" do Dummy.restrict!('!').first.fluffies.protector_subject.should == '!' Dummy.first.restrict!('!').fluffies.protector_subject.should == '!' Dummy.restrict!('!').first.fluffies.new.protector_subject.should == '!' Dummy.first.restrict!('!').fluffies.new.protector_subject.should == '!' Dummy.first.fluffies.restrict!('!').new.protector_subject.should == '!' end context "with open relation" do context "adequate", paranoid: false do it "checks existence" do Dummy.first.fluffies.any?.should == true Dummy.first.restrict!('!').fluffies.any?.should == true Dummy.first.fluffies.restrict!('!').any?.should == true end it "counts" do Dummy.first.fluffies.count.should == 2 fluffies = Dummy.first.restrict!('!').fluffies fluffies.count.should == 2 fluffies.protector_subject?.should == true fluffies = Dummy.first.fluffies.restrict!('!') fluffies.count.should == 2 fluffies.protector_subject?.should == true end it "fetches" do Dummy.first.fluffies.count.should == 2 Dummy.first.restrict!('!').fluffies.length.should == 2 Dummy.first.fluffies.restrict!('!').length.should == 2 end end context "paranoid", paranoid: true do it "checks existence" do Dummy.first.fluffies.any?.should == true Dummy.first.restrict!('!').fluffies.any?.should == false Dummy.first.fluffies.restrict!('!').any?.should == false end it "counts" do Dummy.first.fluffies.count.should == 2 fluffies = Dummy.first.restrict!('!').fluffies fluffies.count.should == 0 fluffies.protector_subject?.should == true fluffies = Dummy.first.fluffies.restrict!('!') fluffies.count.should == 0 fluffies.protector_subject?.should == true end it "fetches" do Dummy.first.fluffies.count.should == 2 Dummy.first.restrict!('!').fluffies.length.should == 0 Dummy.first.fluffies.restrict!('!').length.should == 0 end end end end context "with null relation" do it "checks existence" do Dummy.first.fluffies.any?.should == true Dummy.first.restrict!('-').fluffies.any?.should == false Dummy.first.fluffies.restrict!('-').any?.should == false end it "counts" do Dummy.first.fluffies.count.should == 2 fluffies = Dummy.first.restrict!('-').fluffies fluffies.count.should == 0 fluffies.protector_subject?.should == true fluffies = Dummy.first.fluffies.restrict!('-') fluffies.count.should == 0 fluffies.protector_subject?.should == true end it "fetches" do Dummy.first.fluffies.count.should == 2 Dummy.first.restrict!('-').fluffies.length.should == 0 Dummy.first.fluffies.restrict!('-').length.should == 0 end end context "with active relation" do it "checks existence" do Dummy.first.fluffies.any?.should == true Dummy.first.restrict!('+').fluffies.any?.should == true Dummy.first.fluffies.restrict!('+').any?.should == true end it "counts" do Dummy.first.fluffies.count.should == 2 fluffies = Dummy.first.restrict!('+').fluffies fluffies.count.should == 1 fluffies.protector_subject?.should == true fluffies = Dummy.first.fluffies.restrict!('+') fluffies.count.should == 1 fluffies.protector_subject?.should == true end it "fetches" do Dummy.first.fluffies.count.should == 2 Dummy.first.restrict!('+').fluffies.length.should == 1 Dummy.first.fluffies.restrict!('+').length.should == 1 end end end # # Eager loading # describe Protector::Adapters::ActiveRecord::Preloader do describe "eager loading" do it "scopes" do d = Dummy.restrict!('+').includes(:fluffies) d.length.should == 2 d.first.fluffies.length.should == 1 end context "joined to filtered association" do it "scopes" do d = Dummy.restrict!('+').includes(:fluffies).where(fluffies: {string: 'zomgstring'}) d.length.should == 2 d.first.fluffies.length.should == 1 end end context "joined to plain association" do it "scopes" do d = Dummy.restrict!('+').includes(:bobbies, :fluffies).where( bobbies: {string: 'zomgstring'}, fluffies: {string: 'zomgstring'} ) d.length.should == 2 d.first.fluffies.length.should == 1 d.first.bobbies.length.should == 2 end end context "with complex include" do it "scopes" do d = Dummy.restrict!('+').includes(fluffies: :loony).where( fluffies: {string: 'zomgstring'}, loonies: {string: 'zomgstring'} ) d.length.should == 2 d.first.fluffies.length.should == 1 d.first.fluffies.first.loony.should be_a_kind_of(Loony) end end end context "complicated features" do # https://github.com/inossidabile/protector/commit/7ce072aa2074e0f3b48e293b952810f720bc143d it "handles scopes with includes" do fluffy = Class.new(ActiveRecord::Base) do def self.name; 'Fluffy'; end def self.model_name; ActiveModel::Name.new(self, nil, "fluffy"); end self.table_name = "fluffies" scope :none, where('1 = 0') unless respond_to?(:none) belongs_to :dummy, class_name: 'Dummy' protect do scope { includes(:dummy).where(dummies: {id: 1}) } end end expect { fluffy.restrict!('!').to_a }.to_not raise_error end # https://github.com/inossidabile/protector/issues/42 if ActiveRecord::Base.respond_to?(:enum) context "enums" do before(:each) do dummy.instance_eval do enum number: [ :active, :archived ] end end it "can be read" do dummy.instance_eval do protect do can :read, :number can :create, :number can :update, :number end end d = dummy.new.restrict!('!') expect { d.active! }.to_not raise_error d.number.should == 'active' d.active?.should == true d.archived?.should == false d.delete end end end end end end end