diff --git a/app/models/ability.rb b/app/models/ability.rb index 1ee62162a6..c8dcf992ff 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,7 +22,8 @@ class Ability :marker_permissions, :encode_dashboard_permissions, :timeline_permissions, - :checkout_permissions] + :checkout_permissions, + :repository_read_only_permissions] # Override to add handling of SpeedyAF proxy objects def edit_permissions @@ -260,6 +261,16 @@ def checkout_permissions end end + def repository_read_only_permissions + if Settings.repository_read_only_mode + cannot [:create, :edit, :update, :destroy, :update_access_control, :unpublish], [MediaObject, SpeedyAF::Proxy::MediaObject] + cannot [:create, :edit, :update, :destroy], [MasterFile, SpeedyAF::Proxy::MasterFile] + cannot [:create, :edit, :update, :destroy], [Derivative, SpeedyAF::Proxy::Derivative] + cannot [:create, :edit, :update, :destroy, :update_unit, :update_access_control, :update_managers, :update_editors, :update_depositors], [Admin::Collection, SpeedyAF::Proxy::Admin::Collection] + cannot [:create, :edit, :update, :destroy], SpeedyAF::Base + end + end + def is_administrator? @user_groups.include?("administrator") end @@ -289,5 +300,4 @@ def is_api_request? @json_api_login ||= false @json_api_login end - end diff --git a/config/initializers/active_fedora_general.rb b/config/initializers/active_fedora_general.rb index 968cb63305..0e17e96e41 100644 --- a/config/initializers/active_fedora_general.rb +++ b/config/initializers/active_fedora_general.rb @@ -209,3 +209,9 @@ def initialize(file) get_values(:type) << self.class.type end end + +ActiveFedora::Common.module_eval do + def readonly? + @readonly || Settings.repository_read_only_mode + end +end diff --git a/config/settings.yml b/config/settings.yml index ef52e1131b..687ea27dcd 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -129,4 +129,9 @@ derivative: # If the default system /tmp directory is storage constrained, you can define an alternative here. # Leave commented out to use the system default. # tempfile: -# location: '/tmp' \ No newline at end of file +# location: '/tmp' + +# Enable read-only mode for disabling all save interactions with fedora and solr +# Does not affect actions that only affect the database +# This is useful when running long migrations +repository_read_only_mode: false diff --git a/spec/lib/active_fedora_base.rb b/spec/lib/active_fedora_base.rb new file mode 100644 index 0000000000..eb17ac156c --- /dev/null +++ b/spec/lib/active_fedora_base.rb @@ -0,0 +1,41 @@ +# Copyright 2011-2024, The Trustees of Indiana University and Northwestern +# University. Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# --- END LICENSE_HEADER BLOCK --- + +require 'rails_helper' + +describe 'ActiveFedora::Base' do + let(:new_obj) { ActiveFedora::Base.new } + let(:obj) { ActiveFedora::Base.create } + + # This tests changes overrides that are in config/initializers/active_fedora_general.rb + context 'when read-only mode' do + before { allow(Settings).to receive(:repository_read_only_mode).and_return(true) } + + it 'raises ReadOnlyRecord for any write operation' do + expect { ActiveFedora::Base.create }.to raise_error ActiveFedora::ReadOnlyRecord + expect { new_obj.save }.to raise_error ActiveFedora::ReadOnlyRecord + expect { new_obj.save! }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.save }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.save! }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.update }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.update! }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.delete }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.destroy }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.destroy! }.to raise_error ActiveFedora::ReadOnlyRecord + expect { obj.eradicate }.to raise_error ActiveFedora::ReadOnlyRecord + expect { ActiveFedora::Base.eradicate(obj.to_uri) }.to raise_error ActiveFedora::ReadOnlyRecord + expect { ActiveFedora::Base.delete_tombstone(obj.to_uri) }.to raise_error ActiveFedora::ReadOnlyRecord + end + end +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 914b6aa858..c4662cda5b 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -20,4 +20,169 @@ expect(Ability.new(nil).user_groups).to eq ["public"] end end + + describe 'repository read-only mode' do + # Next line is let! to ensure that it runs before the before block which would stop the object from being created + let!(:media_object) { FactoryBot.create(:media_object) } + let(:media_object_proxy) { SpeedyAF::Base.find(media_object.id) } + let(:collection) { media_object.collection } + let(:collection_proxy) { SpeedyAF::Base.find(collection.id) } + let(:admin) { FactoryBot.create(:administrator) } + let(:session) { {} } + subject(:admin_ability) { Ability.new(admin, session) } + + before { allow(Settings).to receive(:repository_read_only_mode).and_return(read_only) } + + context 'with read-only enabled' do + let(:read_only) { true } + + it 'has read-only abilities' do + expect(subject.can?(:manage, :all)).to eq true + expect(subject.can?(:manage, MediaObject)).to eq true + expect(subject.can?(:discover_everything, MediaObject)).to eq true + + expect(subject.can?(:read, media_object)).to eq true + expect(subject.can?(:read, media_object_proxy)).to eq true + expect(subject.can?(:read, collection)).to eq true + expect(subject.can?(:read, collection_proxy)).to eq true + + expect(subject.can?(:create, MediaObject)).to eq false + expect(subject.can?(:read, MediaObject)).to eq true + expect(subject.can?(:edit, MediaObject)).to eq false + expect(subject.can?(:update, MediaObject)).to eq false + expect(subject.can?(:destroy, MediaObject)).to eq false + expect(subject.can?(:update_access_control, MediaObject)).to eq false + expect(subject.can?(:unpublish, MediaObject)).to eq false + + expect(subject.can?(:create, SpeedyAF::Proxy::MediaObject)).to eq false + expect(subject.can?(:read, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::MediaObject)).to eq false + expect(subject.can?(:update, SpeedyAF::Proxy::MediaObject)).to eq false + expect(subject.can?(:destroy, SpeedyAF::Proxy::MediaObject)).to eq false + expect(subject.can?(:update_access_control, SpeedyAF::Proxy::MediaObject)).to eq false + expect(subject.can?(:unpublish, SpeedyAF::Proxy::MediaObject)).to eq false + + expect(subject.can?(:create, MasterFile)).to eq false + expect(subject.can?(:read, MasterFile)).to eq true + expect(subject.can?(:edit, MasterFile)).to eq false + expect(subject.can?(:update, MasterFile)).to eq false + expect(subject.can?(:destroy, MasterFile)).to eq false + + expect(subject.can?(:create, SpeedyAF::Proxy::MasterFile)).to eq false + expect(subject.can?(:read, SpeedyAF::Proxy::MasterFile)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::MasterFile)).to eq false + expect(subject.can?(:update, SpeedyAF::Proxy::MasterFile)).to eq false + expect(subject.can?(:destroy, SpeedyAF::Proxy::MasterFile)).to eq false + + expect(subject.can?(:create, Derivative)).to eq false + expect(subject.can?(:read, Derivative)).to eq true + expect(subject.can?(:edit, Derivative)).to eq false + expect(subject.can?(:update, Derivative)).to eq false + expect(subject.can?(:destroy, Derivative)).to eq false + + expect(subject.can?(:create, SpeedyAF::Proxy::Derivative)).to eq false + expect(subject.can?(:read, SpeedyAF::Proxy::Derivative)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::Derivative)).to eq false + expect(subject.can?(:update, SpeedyAF::Proxy::Derivative)).to eq false + expect(subject.can?(:destroy, SpeedyAF::Proxy::Derivative)).to eq false + + expect(subject.can?(:create, Admin::Collection)).to eq false + expect(subject.can?(:read, Admin::Collection)).to eq true + expect(subject.can?(:edit, Admin::Collection)).to eq false + expect(subject.can?(:update, Admin::Collection)).to eq false + expect(subject.can?(:destroy, Admin::Collection)).to eq false + expect(subject.can?(:update_unit, Admin::Collection)).to eq false + expect(subject.can?(:update_access_control, Admin::Collection)).to eq false + expect(subject.can?(:update_managers, Admin::Collection)).to eq false + expect(subject.can?(:update_editors, Admin::Collection)).to eq false + expect(subject.can?(:update_depositors, Admin::Collection)).to eq false + + expect(subject.can?(:create, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:read, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:destroy, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update_unit, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update_access_control, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update_managers, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update_editors, SpeedyAF::Proxy::Admin::Collection)).to eq false + expect(subject.can?(:update_depositors, SpeedyAF::Proxy::Admin::Collection)).to eq false + end + end + + context 'with read-only disabled' do + let(:read_only) { false } + + it 'has all abilities' do + expect(subject.can?(:manage, :all)).to eq true + expect(subject.can?(:manage, MediaObject)).to eq true + expect(subject.can?(:discover_everything, MediaObject)).to eq true + + expect(subject.can?(:read, media_object)).to eq true + expect(subject.can?(:read, collection)).to eq true + + expect(subject.can?(:create, MediaObject)).to eq true + expect(subject.can?(:read, MediaObject)).to eq true + expect(subject.can?(:edit, MediaObject)).to eq true + expect(subject.can?(:update, MediaObject)).to eq true + expect(subject.can?(:destroy, MediaObject)).to eq true + expect(subject.can?(:update_access_control, MediaObject)).to eq true + expect(subject.can?(:unpublish, MediaObject)).to eq true + + expect(subject.can?(:create, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:read, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:update, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:destroy, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:update_access_control, SpeedyAF::Proxy::MediaObject)).to eq true + expect(subject.can?(:unpublish, SpeedyAF::Proxy::MediaObject)).to eq true + + expect(subject.can?(:create, MasterFile)).to eq true + expect(subject.can?(:read, MasterFile)).to eq true + expect(subject.can?(:edit, MasterFile)).to eq true + expect(subject.can?(:update, MasterFile)).to eq true + expect(subject.can?(:destroy, MasterFile)).to eq true + + expect(subject.can?(:create, SpeedyAF::Proxy::MasterFile)).to eq true + expect(subject.can?(:read, SpeedyAF::Proxy::MasterFile)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::MasterFile)).to eq true + expect(subject.can?(:update, SpeedyAF::Proxy::MasterFile)).to eq true + expect(subject.can?(:destroy, SpeedyAF::Proxy::MasterFile)).to eq true + + expect(subject.can?(:create, Derivative)).to eq true + expect(subject.can?(:read, Derivative)).to eq true + expect(subject.can?(:edit, Derivative)).to eq true + expect(subject.can?(:update, Derivative)).to eq true + expect(subject.can?(:destroy, Derivative)).to eq true + + expect(subject.can?(:create, SpeedyAF::Proxy::Derivative)).to eq true + expect(subject.can?(:read, SpeedyAF::Proxy::Derivative)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::Derivative)).to eq true + expect(subject.can?(:update, SpeedyAF::Proxy::Derivative)).to eq true + expect(subject.can?(:destroy, SpeedyAF::Proxy::Derivative)).to eq true + + expect(subject.can?(:create, Admin::Collection)).to eq true + expect(subject.can?(:read, Admin::Collection)).to eq true + expect(subject.can?(:edit, Admin::Collection)).to eq true + expect(subject.can?(:update, Admin::Collection)).to eq true + expect(subject.can?(:destroy, Admin::Collection)).to eq true + expect(subject.can?(:update_unit, Admin::Collection)).to eq true + expect(subject.can?(:update_access_control, Admin::Collection)).to eq true + expect(subject.can?(:update_managers, Admin::Collection)).to eq true + expect(subject.can?(:update_editors, Admin::Collection)).to eq true + expect(subject.can?(:update_depositors, Admin::Collection)).to eq true + + expect(subject.can?(:create, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:read, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:edit, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:destroy, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update_unit, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update_access_control, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update_managers, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update_editors, SpeedyAF::Proxy::Admin::Collection)).to eq true + expect(subject.can?(:update_depositors, SpeedyAF::Proxy::Admin::Collection)).to eq true + end + end + end end