From c00c03737edde766dafd96ad15270a448dbd91ba Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Wed, 19 Aug 2020 04:29:49 +0100 Subject: [PATCH] Bring user and ability related files from spree_core --- app/models/spree/ability.rb | 73 ++++++++ spec/models/spree/ability_spec.rb | 265 ++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 app/models/spree/ability.rb create mode 100644 spec/models/spree/ability_spec.rb diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb new file mode 100644 index 0000000000..f29dea39e1 --- /dev/null +++ b/app/models/spree/ability.rb @@ -0,0 +1,73 @@ +# Implementation class for Cancan gem. Instead of overriding this class, consider adding new permissions +# using the special +register_ability+ method which allows extensions to add their own abilities. +# +# See http://github.com/ryanb/cancan for more details on cancan. +require 'cancan' + +module Spree + class Ability + include CanCan::Ability + + class_attribute :abilities + self.abilities = Set.new + + # Allows us to go beyond the standard cancan initialize method which makes it difficult for engines to + # modify the default +Ability+ of an application. The +ability+ argument must be a class that includes + # the +CanCan::Ability+ module. The registered ability should behave properly as a stand-alone class + # and therefore should be easy to test in isolation. + def self.register_ability(ability) + self.abilities.add(ability) + end + + def self.remove_ability(ability) + self.abilities.delete(ability) + end + + def initialize(user) + self.clear_aliased_actions + + # override cancan default aliasing (we don't want to differentiate between read and index) + alias_action :delete, to: :destroy + alias_action :edit, to: :update + alias_action :new, to: :create + alias_action :new_action, to: :create + alias_action :show, to: :read + + user ||= Spree.user_class.new + + if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin') + can :manage, :all + else + can [:index, :read], Country + can [:index, :read], OptionType + can [:index, :read], OptionValue + can :create, Order + can :read, Order do |order, token| + order.user == user || order.token && token == order.token + end + can :update, Order do |order, token| + order.user == user || order.token && token == order.token + end + can [:index, :read], Product + can [:index, :read], ProductProperty + can [:index, :read], Property + can :create, Spree.user_class + can [:read, :update, :destroy], Spree.user_class, id: user.id + can [:index, :read], State + can [:index, :read], StockItem + can [:index, :read], StockLocation + can [:index, :read], StockMovement + can [:index, :read], Taxon + can [:index, :read], Taxonomy + can [:index, :read], Variant + can [:index, :read], Zone + end + + # Include any abilities registered by extensions, etc. + Ability.abilities.each do |clazz| + ability = clazz.send(:new, user) + @rules = rules + ability.send(:rules) + end + end + end +end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb new file mode 100644 index 0000000000..2ee7e0d613 --- /dev/null +++ b/spec/models/spree/ability_spec.rb @@ -0,0 +1,265 @@ +require 'spec_helper' +require 'cancan/matchers' +require 'spree/testing_support/ability_helpers' +require 'spree/testing_support/bar_ability' + +# Fake ability for testing registration of additional abilities +class FooAbility + include CanCan::Ability + + def initialize(user) + # allow anyone to perform index on Order + can :index, Spree::Order + # allow anyone to update an Order with id of 1 + can :update, Spree::Order do |order| + order.id == 1 + end + end +end + +describe Spree::Ability do + let(:user) { create(:user) } + let(:ability) { Spree::Ability.new(user) } + let(:token) { nil } + + before do + user.spree_roles.clear + end + + TOKEN = 'token123' + + after(:each) { + Spree::Ability.abilities = Set.new + user.spree_roles = [] + } + + context 'register_ability' do + it 'should add the ability to the list of abilties' do + Spree::Ability.register_ability(FooAbility) + Spree::Ability.new(user).abilities.should_not be_empty + end + + it 'should apply the registered abilities permissions' do + Spree::Ability.register_ability(FooAbility) + Spree::Ability.new(user).can?(:update, mock_model(Spree::Order, :id => 1)).should be_true + end + end + + context 'for general resource' do + let(:resource) { Object.new } + + context 'with admin user' do + before(:each) { user.stub(:has_spree_role?).and_return(true) } + it_should_behave_like 'access granted' + it_should_behave_like 'index allowed' + end + + context 'with customer' do + it_should_behave_like 'access denied' + it_should_behave_like 'no index allowed' + end + end + + context 'for admin protected resources' do + let(:resource) { Object.new } + let(:resource_shipment) { Spree::Shipment.new } + let(:resource_product) { Spree::Product.new } + let(:resource_user) { Spree.user_class.new } + let(:resource_order) { Spree::Order.new } + let(:fakedispatch_user) { Spree.user_class.new } + let(:fakedispatch_ability) { Spree::Ability.new(fakedispatch_user) } + + context 'with admin user' do + it 'should be able to admin' do + user.spree_roles << Spree::Role.find_or_create_by(name: 'admin') + ability.should be_able_to :admin, resource + ability.should be_able_to :index, resource_order + ability.should be_able_to :show, resource_product + ability.should be_able_to :create, resource_user + end + end + + context 'with fakedispatch user' do + it 'should be able to admin on the order and shipment pages' do + user.spree_roles << Spree::Role.find_or_create_by(name: 'bar') + + Spree::Ability.register_ability(BarAbility) + + ability.should_not be_able_to :admin, resource + + ability.should be_able_to :admin, resource_order + ability.should be_able_to :index, resource_order + ability.should_not be_able_to :update, resource_order + # ability.should_not be_able_to :create, resource_order # Fails + + ability.should be_able_to :admin, resource_shipment + ability.should be_able_to :index, resource_shipment + ability.should be_able_to :create, resource_shipment + + ability.should_not be_able_to :admin, resource_product + ability.should_not be_able_to :update, resource_product + # ability.should_not be_able_to :show, resource_product # Fails + + ability.should_not be_able_to :admin, resource_user + ability.should_not be_able_to :update, resource_user + ability.should be_able_to :update, user + # ability.should_not be_able_to :create, resource_user # Fails + # It can create new users if is has access to the :admin, User!! + + # TODO change the Ability class so only users and customers get the extra premissions? + + Spree::Ability.remove_ability(BarAbility) + end + end + + context 'with customer' do + it 'should not be able to admin' do + ability.should_not be_able_to :admin, resource + ability.should_not be_able_to :admin, resource_order + ability.should_not be_able_to :admin, resource_product + ability.should_not be_able_to :admin, resource_user + end + end + end + + context 'as Guest User' do + + context 'for Country' do + let(:resource) { Spree::Country.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for OptionType' do + let(:resource) { Spree::OptionType.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for OptionValue' do + let(:resource) { Spree::OptionType.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for Order' do + let(:resource) { Spree::Order.new } + + context 'requested by same user' do + before(:each) { resource.user = user } + it_should_behave_like 'access granted' + it_should_behave_like 'no index allowed' + end + + context 'requested by other user' do + before(:each) { resource.user = Spree.user_class.new } + it_should_behave_like 'create only' + end + + context 'requested with proper token' do + let(:token) { 'TOKEN123' } + before(:each) { resource.stub :token => 'TOKEN123' } + it_should_behave_like 'access granted' + it_should_behave_like 'no index allowed' + end + + context 'requested with inproper token' do + let(:token) { 'FAIL' } + before(:each) { resource.stub :token => 'TOKEN123' } + it_should_behave_like 'create only' + end + end + + context 'for Product' do + let(:resource) { Spree::Product.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for ProductProperty' do + let(:resource) { Spree::Product.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for Property' do + let(:resource) { Spree::Product.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for State' do + let(:resource) { Spree::State.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for StockItem' do + let(:resource) { Spree::StockItem.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for StockLocation' do + let(:resource) { Spree::StockLocation.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for StockMovement' do + let(:resource) { Spree::StockMovement.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for Taxons' do + let(:resource) { Spree::Taxon.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for Taxonomy' do + let(:resource) { Spree::Taxonomy.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for User' do + context 'requested by same user' do + let(:resource) { user } + it_should_behave_like 'access granted' + it_should_behave_like 'no index allowed' + end + context 'requested by other user' do + let(:resource) { Spree.user_class.new } + it_should_behave_like 'create only' + end + end + + context 'for Variant' do + let(:resource) { Spree::Variant.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + + context 'for Zone' do + let(:resource) { Spree::Zone.new } + context 'requested by any user' do + it_should_behave_like 'read only' + end + end + end +end