diff --git a/app/models/cocina.rb b/app/models/cocina.rb index 9fc16407..6492748c 100644 --- a/app/models/cocina.rb +++ b/app/models/cocina.rb @@ -44,13 +44,6 @@ def find_file(file_name) .find { |file| file['filename'] == file_name } || raise(ActionController::MissingFile, "File not found '#{file_name}'") end - def find_file_md5(file_name) - file_node = find_file(file_name) - file_node.fetch('hasMessageDigests') - .find { |digest_node| digest_node.fetch('type') == 'md5' } - .fetch('digest') - end - def thumbnail_file data.dig('structural', 'contains') .lazy.flat_map { |file_set| file_set.dig('structural', 'contains') } diff --git a/app/models/stacks_file.rb b/app/models/stacks_file.rb index 4be6c7a1..76b2897f 100644 --- a/app/models/stacks_file.rb +++ b/app/models/stacks_file.rb @@ -9,7 +9,6 @@ class StacksFile def initialize(file_name:, cocina:) @file_name = file_name @cocina = cocina - validate! end attr_reader :file_name, :cocina @@ -18,7 +17,7 @@ def id cocina.druid end - validates :file_name, presence: true + validates :id, format: { with: StorageRoot::DRUID_PARTS_PATTERN } # Some files exist but have unreadable permissions, treat these as non-existent def readable? @@ -41,7 +40,6 @@ def path @path ||= storage_root.absolute_path end - # Used as the IIIF identifier for retrieving this file from the image server def treeified_path storage_root.relative_path end diff --git a/app/models/storage_root.rb b/app/models/storage_root.rb index 3cd2e8ec..6e4d6052 100644 --- a/app/models/storage_root.rb +++ b/app/models/storage_root.rb @@ -13,9 +13,21 @@ def initialize(file_name:, cocina:) delegate :druid, to: :cocina - delegate :absolute_path, to: :path_finder + def druid_parts + @druid_parts ||= druid.match(DRUID_PARTS_PATTERN) + end + + def absolute_path + return unless relative_path - delegate :relative_path, to: :path_finder + path_finder.absolute_path.to_s + end + + def relative_path + return unless druid_parts && file_name + + path_finder.relative_path.to_s + end def treeified_id File.join(druid_parts[1..4]) @@ -26,41 +38,26 @@ def treeified_id attr_reader :cocina, :file_name def path_finder - @path_finder ||= path_finder_class.new(treeified_id:, file_name:, cocina:) + @path_finder ||= path_finder_class.new(treeified_id:, druid:, file_name:) end def path_finder_class LegacyPathFinder end - def druid_parts - @druid_parts ||= druid.match(DRUID_PARTS_PATTERN) - end - # Calculate file paths in the legacy Stacks structure class LegacyPathFinder - def initialize(treeified_id:, file_name:, cocina:) + def initialize(treeified_id:, file_name:, druid:) # rubocop:disable Lint/UnusedMethodArgument @treeified_id = treeified_id @file_name = file_name - @cocina = cocina end - # As this is used for external service URLs (Canteloupe image server), we don't want to put content addressable path here.' def relative_path File.join(@treeified_id, @file_name) end def absolute_path - return content_addressable_path if File.exist?(content_addressable_path) - File.join(Settings.stacks.storage_root, relative_path) end - - def content_addressable_path - @content_addressable_path ||= begin - md5 = @cocina.find_file_md5(@file_name) - File.join(Settings.stacks.content_addressable_storage_root, @treeified_id, @cocina.druid, 'content', md5) - end - end end end diff --git a/config/settings.yml b/config/settings.yml index a669172a..8f6b591f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -4,7 +4,6 @@ features: stacks: storage_root: /stacks - content_addressable_storage_root: /stacks/content_addressable imageserver: base_uri: "http://imageserver-prod.stanford.edu/iiif/2/" diff --git a/spec/controllers/file_controller_spec.rb b/spec/controllers/file_controller_spec.rb index 8e5aec34..8dd1fa51 100644 --- a/spec/controllers/file_controller_spec.rb +++ b/spec/controllers/file_controller_spec.rb @@ -8,7 +8,26 @@ end let(:public_json) do - Factories.cocina_with_file + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end describe '#show' do diff --git a/spec/controllers/legacy_image_service_controller_spec.rb b/spec/controllers/legacy_image_service_controller_spec.rb index 22f46cac..9ceafe47 100644 --- a/spec/controllers/legacy_image_service_controller_spec.rb +++ b/spec/controllers/legacy_image_service_controller_spec.rb @@ -9,7 +9,6 @@ let(:public_json) do { - 'externalIdentifier' => 'druid:nr349ct7889', 'structural' => { 'contains' => [ { diff --git a/spec/factories/cocina.rb b/spec/factories/cocina.rb deleted file mode 100644 index 6ac6af64..00000000 --- a/spec/factories/cocina.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Factories - def self.cocina(id: "druid:nr349ct7889") - { "externalIdentifier" => id } - end - - def self.cocina_with_file(id: "druid:nr349ct7889", file_name: 'image.jp2', access: {}, - file_access: { 'view' => 'world', 'download' => 'world' }, - mime_type: 'image/jp2') - cocina(id:).merge( - 'access' => access, - 'structural' => { - 'contains' => [ - { - 'structural' => { - 'contains' => [ - { - 'filename' => file_name, - 'hasMessageDigests' => [ - { 'type' => 'sha1', 'digest' => 'b1a2922356709cc53b85f1b8027982d23b573f80' }, - { 'type' => 'md5', 'digest' => '02f77c96c40ad3c7c843baa9c7b2ff2c' } - ], - 'hasMimeType' => mime_type, - 'access' => file_access - } - ] - } - } - ] - } - ) - end -end diff --git a/spec/models/stacks_file_spec.rb b/spec/models/stacks_file_spec.rb index 14a6af95..cca03399 100644 --- a/spec/models/stacks_file_spec.rb +++ b/spec/models/stacks_file_spec.rb @@ -5,19 +5,10 @@ RSpec.describe StacksFile do let(:druid) { 'nr349ct7889' } let(:file_name) { 'image.jp2' } - let(:cocina) { Cocina.new(public_json) } + let(:cocina) { Cocina.new({ 'externalIdentifier' => druid }) } let(:instance) { described_class.new(file_name:, cocina:) } let(:path) { storage_root.absolute_path } let(:storage_root) { StorageRoot.new(cocina:, file_name:) } - let(:public_json) { Factories.cocina_with_file } - - context 'with a missing file name' do - let(:file_name) { nil } - - it 'raises an error' do - expect { instance }.to raise_error ActiveModel::ValidationError - end - end describe '#path' do subject { instance.path } @@ -25,6 +16,18 @@ it 'is the druid tree path to the file' do expect(subject).to eq(path) end + + context 'with a malformed druid' do + let(:druid) { 'abcdef' } + + it { is_expected.to be_nil } + end + + context 'with a missing file name' do + let(:file_name) { nil } + + it { is_expected.to be_nil } + end end describe '#readable?' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 72998b92..d870e9ee 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -11,8 +11,6 @@ require 'spec_helper' require 'rspec/rails' require 'capybara/rails' - -require_relative 'factories/cocina' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in diff --git a/spec/requests/file_auth_request_spec.rb b/spec/requests/file_auth_request_spec.rb index 2b330383..c3ee7412 100644 --- a/spec/requests/file_auth_request_spec.rb +++ b/spec/requests/file_auth_request_spec.rb @@ -25,7 +25,26 @@ # NOTE: stanford only + location rights tested under location context context 'stanford only (no location qualifications)' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'stanford', 'download' => 'stanford' }) + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'stanford', + 'download' => 'stanford' + } + } + ] + } + } + ] + } + } end context 'webauthed user' do @@ -50,8 +69,27 @@ context 'location' do context 'not stanford qualified in any way' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', - 'location' => 'location1' }) + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'location-based', + 'download' => 'location-based', + 'location' => 'location1' + } + } + ] + } + } + ] + } + } end it 'allows when user in location' do diff --git a/spec/requests/file_spec.rb b/spec/requests/file_spec.rb index b41de18f..4e1a3a3c 100644 --- a/spec/requests/file_spec.rb +++ b/spec/requests/file_spec.rb @@ -10,7 +10,26 @@ let(:druid) { 'nr349ct7889' } let(:file_name) { 'image.jp2' } let(:public_json) do - Factories.cocina_with_file + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end describe 'OPTIONS options' do @@ -25,7 +44,26 @@ describe 'GET file with slashes in filename' do let(:file_name) { 'path/to/image.jp2' } let(:public_json) do - Factories.cocina_with_file(file_name:) + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end before do diff --git a/spec/requests/iiif/auth/v2/probe_service_spec.rb b/spec/requests/iiif/auth/v2/probe_service_spec.rb index 79590bd3..87d6682c 100644 --- a/spec/requests/iiif/auth/v2/probe_service_spec.rb +++ b/spec/requests/iiif/auth/v2/probe_service_spec.rb @@ -7,7 +7,7 @@ let(:file_name) { 'image.jp2' } let(:stacks_uri) { "https://stacks-uat.stanford.edu/file/druid:#{id}/#{URI.encode_uri_component(file_name)}" } let(:stacks_uri_param) { URI.encode_uri_component(stacks_uri) } - let(:public_json) { Factories.cocina } + let(:public_json) { { "externalIdentifier" => "druid:nr349ct7889" } } # NOTE: For any unauthorized responses, the status from the service is OK...the access status of the resource is in the response body @@ -73,7 +73,26 @@ context 'when the user has access to the resource because it is world accessible' do let(:public_json) do - Factories.cocina_with_file(file_name:) + { + "externalIdentifier" => "druid:nr349ct7889", + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end before do @@ -121,7 +140,26 @@ context 'when the user has access to the resource and it is streamable' do let(:file_name) { 'SC0193_1982-013_b06_f01_1981-09-29.mp4' } let(:public_json) do - Factories.cocina_with_file(file_name:) + { + "externalIdentifier" => "druid:nr349ct7889", + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end let(:stacks_uri) { "https://stacks-uat.stanford.edu/file/#{id}/#{URI.encode_uri_component(file_name)}" } @@ -147,10 +185,6 @@ get "/iiif/auth/v2/probe?id=#{stacks_uri_param}" end - let(:public_json) do - Factories.cocina_with_file(file_name:) - end - it 'returns a 404 response' do expect(response).to have_http_status :ok expect(response.parsed_body).to include({ @@ -163,7 +197,26 @@ context 'when a Stanford only resource' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'stanford', 'download' => 'stanford' }, file_name:) + { + "externalIdentifier" => "druid:nr349ct7889", + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'stanford', + 'download' => 'stanford' + } + } + ] + } + } + ] + } + } end context 'when the user has a bearer token with the ldap group' do @@ -225,7 +278,27 @@ context 'when the user does not have access to a location restricted resource' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', 'location' => location }) + { + "externalIdentifier" => "druid:nr349ct7889", + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'location-based', + 'download' => 'location_based', + 'location' => location + } + } + ] + } + } + ] + } + } end before do @@ -269,8 +342,31 @@ context 'when the user does not have access to a stanford restricted embargoed resource' do let(:public_json) do - Factories.cocina_with_file(access: { 'embargo' => { "releaseDate" => Time.parse('2099-05-15').getlocal.as_json } }, - file_access: { 'view' => 'stanford', 'download' => 'stanford' }) + { + "externalIdentifier" => "druid:nr349ct7889", + 'access' => { + 'embargo' => { + "releaseDate" => Time.parse('2099-05-15').getlocal.as_json + } + }, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'stanford', + 'download' => 'stanford' + } + } + ] + } + } + ] + } + } end before do @@ -294,8 +390,31 @@ context 'when the user does not have access to an embargoed resource' do let(:public_json) do - Factories.cocina_with_file(access: { 'embargo' => { "releaseDate" => Time.parse('2099-05-15').getlocal.as_json } }, - file_access: { 'view' => 'none', 'download' => 'none' }) + { + "externalIdentifier" => "druid:nr349ct7889", + 'access' => { + 'embargo' => { + "releaseDate" => Time.parse('2099-05-15').getlocal.as_json + } + }, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'none', + 'download' => 'none' + } + } + ] + } + } + ] + } + } end before do diff --git a/spec/requests/iiif_auth_request_spec.rb b/spec/requests/iiif_auth_request_spec.rb index 221fc6ae..cd01694d 100644 --- a/spec/requests/iiif_auth_request_spec.rb +++ b/spec/requests/iiif_auth_request_spec.rb @@ -35,7 +35,26 @@ context 'with a public item' do let(:public_json) do - Factories.cocina_with_file + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end context 'with an unauthenticated user' do @@ -51,7 +70,26 @@ context 'with a stanford only item' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'stanford', 'download' => 'stanford' }) + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'stanford', + 'download' => 'stanford' + } + } + ] + } + } + ] + } + } end context 'with a authorized webauthed user' do @@ -83,10 +121,29 @@ end end - context 'with a location-restricted item that is not a thumbnail' do + context 'with a location-restricted item' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', 'location' => 'location1' }, - mime_type: 'image/jpeg') + { + 'externalIdentifier' => druid, + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'location-based', + 'download' => 'location-based', + 'location' => 'location1' + } + } + ] + } + } + ] + } + } end context 'with a user in the location' do diff --git a/spec/requests/iiif_spec.rb b/spec/requests/iiif_spec.rb index 307170e1..971803e0 100644 --- a/spec/requests/iiif_spec.rb +++ b/spec/requests/iiif_spec.rb @@ -15,7 +15,26 @@ end let(:public_json) do - Factories.cocina_with_file + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end before do @@ -51,22 +70,60 @@ end context 'for location-restricted documents' do - context 'outside of the location' do - context 'when the file is not a thumbnail' do - let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', 'location' => 'location1' }, - mime_type: 'image/jpeg') - end + let(:public_json) do + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'location-based', + 'download' => 'location_based', + 'location' => 'location1' + } + } + ] + } + } + ] + } + } + end - it 'uses the unauthorized status code for the response' do - get '/image/iiif/nr349ct7889%2Fimage/info.json' - expect(response).to have_http_status :unauthorized - end + context 'outside of the location' do + it 'uses the unauthorized status code for the response' do + get '/image/iiif/nr349ct7889%2Fimage/info.json' + expect(response).to have_http_status :unauthorized end - context 'when the files is a thumbnail' do + context 'for a thumbnail' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', 'location' => 'location1' }) + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'location-based', + 'download' => 'location_based', + 'location' => 'location1' + }, + 'hasMimeType' => 'image/jp2' + } + ] + } + } + ] + } + } end it 'redirects requests to the degraded info.json' do @@ -80,9 +137,6 @@ context 'at the location' do let(:user) { User.new(ip_address: 'ip.address1') } - let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'location-based', 'download' => 'location-based', 'location' => 'location1' }) - end before do allow_any_instance_of(IiifController).to receive(:current_user).and_return(user) end @@ -95,7 +149,26 @@ context 'for stanford-restricted documents' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'stanford', 'download' => 'stanford' }) + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'stanford', + 'download' => 'stanford' + } + } + ] + } + } + ] + } + } end it 'redirects requests to the degraded info.json' do @@ -117,7 +190,26 @@ context 'where no one can download' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'world', 'download' => 'none' }) + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'world', + 'download' => 'none' + } + } + ] + } + } + ] + } + } end it 'serves up regular info.json (no degraded)' do @@ -135,7 +227,26 @@ context 'where stanford only no download rights' do let(:public_json) do - Factories.cocina_with_file(file_access: { 'view' => 'stanford', 'download' => 'none' }) + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'stanford', + 'download' => 'none' + } + } + ] + } + } + ] + } + } end it 'redirects to degraded version' do diff --git a/spec/requests/metrics_spec.rb b/spec/requests/metrics_spec.rb index 008ba0df..e96539fa 100644 --- a/spec/requests/metrics_spec.rb +++ b/spec/requests/metrics_spec.rb @@ -6,8 +6,27 @@ include ActiveJob::TestHelper let(:druid) { 'nr349ct7889' } let(:file_name) { 'image.jp2' } - let(:public_json) do - Factories.cocina_with_file + let(:json) do + { + 'externalIdentifier' => "druid:#{druid}", + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => file_name, + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + }.to_json end let(:ability) { instance_double(CocinaAbility, can?: true, authorize!: true) } @@ -16,7 +35,7 @@ allow(Settings).to receive(:metrics_api_url).and_return('https://example.com') allow(CocinaAbility).to receive(:new).and_return(ability) stub_request(:post, 'https://example.com/ahoy/events') - stub_request(:get, "https://purl.stanford.edu/#{druid}.json").to_return(status: 200, body: public_json.to_json) + stub_request(:get, "https://purl.stanford.edu/#{druid}.json").to_return(status: 200, body: json) end context 'with an object' do diff --git a/spec/requests/remote_iiif_image_delivery_spec.rb b/spec/requests/remote_iiif_image_delivery_spec.rb index cf54687e..fff015ee 100644 --- a/spec/requests/remote_iiif_image_delivery_spec.rb +++ b/spec/requests/remote_iiif_image_delivery_spec.rb @@ -13,7 +13,26 @@ "http://imageserver-prod.stanford.edu/iiif/2/#{image_server_path('nr349ct7889', 'image.jp2')}/full/max/0/default.jpg" end let(:public_json) do - Factories.cocina_with_file + { + 'externalIdentifier' => 'druid:nr349ct7889', + 'structural' => { + 'contains' => [ + { + 'structural' => { + 'contains' => [ + { + 'filename' => 'image.jp2', + 'access' => { + 'view' => 'world', + 'download' => 'world' + } + } + ] + } + } + ] + } + } end before do