Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using cocina json as the data source #1020

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ Rails:
Metrics/CyclomaticComplexity:
Exclude:
- 'app/models/ability.rb'
- 'app/models/cocina_ability.rb'

Metrics/PerceivedComplexity:
Exclude:
- 'app/models/ability.rb'
- 'app/models/cocina_ability.rb'

Metrics/AbcSize:
Exclude:
- 'app/models/ability.rb'
- 'app/models/cocina_ability.rb'
- 'app/controllers/iiif_controller.rb'

Layout/EmptyLinesAroundBlockBody:
Expand Down Expand Up @@ -64,6 +67,7 @@ Layout/LineLength:
Metrics/MethodLength:
Exclude:
- 'app/models/ability.rb'
- 'app/models/cocina_ability.rb'
- 'app/services/cdl_service.rb'

Naming/HeredocDelimiterNaming:
Expand Down
29 changes: 11 additions & 18 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-11-03 20:55:41 UTC using RuboCop version 1.57.2.
# on 2023-11-08 16:02:12 UTC using RuboCop version 1.57.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -25,12 +25,12 @@ Metrics/AbcSize:
Metrics/CyclomaticComplexity:
Max: 10

# Offense count: 13
# Offense count: 12
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 25

# Offense count: 35
# Offense count: 31
RSpec/AnyInstance:
Exclude:
- 'spec/controllers/media_controller_spec.rb'
Expand All @@ -43,7 +43,7 @@ RSpec/AnyInstance:
- 'spec/requests/iiif_spec.rb'
- 'spec/requests/media_auth_request_spec.rb'

# Offense count: 89
# Offense count: 78
# Configuration parameters: Prefixes, AllowedPatterns.
# Prefixes: when, with, without
RSpec/ContextWording:
Expand Down Expand Up @@ -80,7 +80,7 @@ RSpec/EmptyLineAfterExampleGroup:
- 'spec/controllers/legacy_image_service_controller_spec.rb'
- 'spec/requests/file_auth_request_spec.rb'

# Offense count: 37
# Offense count: 32
# This cop supports safe autocorrection (--autocorrect).
RSpec/EmptyLineAfterFinalLet:
Exclude:
Expand Down Expand Up @@ -159,11 +159,11 @@ RSpec/MessageSpies:
- 'spec/controllers/file_controller_spec.rb'
- 'spec/controllers/media_controller_spec.rb'

# Offense count: 67
# Offense count: 66
RSpec/MultipleExpectations:
Max: 12

# Offense count: 81
# Offense count: 80
# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.
# SupportedStyles: always, named_only
RSpec/NamedSubject:
Expand All @@ -183,7 +183,7 @@ RSpec/NamedSubject:
- 'spec/services/iiif_metadata_service_spec.rb'
- 'spec/services/media_authentication_json_spec.rb'

# Offense count: 66
# Offense count: 54
# Configuration parameters: AllowedGroups.
RSpec/NestedGroups:
Max: 6
Expand Down Expand Up @@ -217,6 +217,7 @@ RSpec/VerifiedDoubles:
- 'spec/services/media_authentication_json_spec.rb'

# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: slashes, arguments
Rails/FilePath:
Expand Down Expand Up @@ -246,7 +247,7 @@ Style/HashAsLastArrayItem:
Exclude:
- 'app/controllers/object_controller.rb'

# Offense count: 18
# Offense count: 13
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
Expand All @@ -266,17 +267,9 @@ Style/StringConcatenation:
- 'spec/controllers/object_controller_spec.rb'
- 'spec/models/stacks_media_token_spec.rb'

# Offense count: 4
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
EnforcedStyle: brackets

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments.
# AllowedMethods: define_method
Style/SymbolProc:
Exclude:
- 'app/controllers/webauth_controller.rb'
119 changes: 119 additions & 0 deletions app/models/cocina_ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# frozen_string_literal: true

##
# User authentication
class CocinaAbility
include CanCan::Ability

def initialize(user)
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
# if user.admin?
# can :manage, :all
# else
# can :read, :all
# end
#
# The first argument to `can` is the action you are giving the user
# permission to do.
# If you pass :manage it will apply to every action. Other common actions
# here are :read, :create, :update and :destroy.
#
# The second argument is the resource the user can perform the action on.
# If you pass :all, it will apply to every resource. Otherwise, pass a Ruby
# class of the resource.
#
# The third argument is an optional hash of conditions to further filter the
# objects. For example, here the user can only update published articles.
# can :update, Article, :published => true
#
# The block argument takes as a parameter an instance of the object for which
# permission is being checked. If the block returns true, the user is granted that
# ability, otherwise the user is denied that ability. The block is only evaluated
# for instances of objects, *not* for classes. If a class is passed to a `can?` or a
# `cannot?` that's defined by a block, that check will *always* grant permission.
#
# See the wiki for details:
# https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
# https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities-with-Blocks

# NOTE: the below ability definitions which reference StacksFile also implicitly
# cover StacksImage and StacksMediaStream, and any other subclasses of StacksFile.

downloadable_models = [StacksFile, StacksImage]
access_models = downloadable_models + [StacksMediaStream]

can :download, downloadable_models do |f|
f.cocina_rights.download == 'world'
end

can [:access], access_models do |f|
f.cocina_rights.view == 'world'
end

if user.stanford?
can :download, downloadable_models do |f|
f.cocina_rights.download == 'stanford'
end

can [:access], access_models do |f|
f.cocina_rights.view == 'stanford'
end
end

if user.locations.present?
can :download, downloadable_models do |f|
next unless f.cocina_rights.download == 'location-based'

user.locations.include?(f.cocina_rights.location)
end

can [:access], access_models do |f|
user.locations.any? do |_location|
next unless f.cocina_rights.view == 'location-based'

user.locations.include?(f.cocina_rights.location)
end
end
end

if user.cdl_tokens.present?
# TODO: Actually check if the CDL object is downloadable
# can [:download, :read], models do |f|
# ...
# end

can [:access], access_models do |f|
next unless f.cocina_rights.controlled_digital_lending?

user.cdl_tokens.any? { |payload| payload['aud'] == f.id }
end
end

cannot :download, RestrictedImage

# These are called when checking to see if the image response should be served
can [:download, :read], Projection do |projection|
can?(:download, projection.image)
end

can [:download, :read], Projection do |projection|
# Allow access to tile or thumbnail-sized requests for an accessible image
(projection.tile? || projection.thumbnail?) && can?(:access, projection.image)
end

can :access, Projection do |projection|
can?(:access, projection.image)
end

can :read, Projection do |projection|
# Allow access to thumbnail-sized projections of a declared (or implicit) thumbnail for the object;
# note that because this is implicit, we do not check rightsMetadata permissions.
projection.thumbnail? && projection.object_thumbnail?
end

alias_action :stream, to: :access
can :read_metadata, StacksImage
end
end
24 changes: 24 additions & 0 deletions app/models/cocina_rights.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

# The rights derived from cocina
class CocinaRights
def initialize(file_rights)
@file_rights = file_rights
end

def download
@file_rights['download']
end

def view
@file_rights['view']
end

def controlled_digital_lending?
@file_rights['controlledDigitalLending']
end

def location
@file_rights['location']
end
end
34 changes: 32 additions & 2 deletions app/models/purl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def self.instance
end

class << self
delegate :public_xml, :files, :barcode, to: :instance
delegate :public_xml, :public_json, :files, :barcode, to: :instance
end

# TODO: was etag a valid key?
Expand All @@ -26,9 +26,35 @@ def public_xml(druid)
end
end

def files(druid)
def public_json(druid)
Rails.cache.fetch("purl/#{druid}.json", expires_in: 10.minutes) do
benchmark "Fetching public json for #{druid}" do
response = Faraday.get(public_json_url(druid))
raise Purl::Exception, response.status unless response.success?

JSON.parse(response.body)
end
end
end

def files(druid, &block)
return to_enum(:files, druid) unless block_given?

Settings.features.cocina ? files_from_json(druid, &block) : files_from_xml(druid, &block)
end

def files_from_json(druid)
doc = public_json(druid)

doc.dig('structural', 'contains').each do |fileset|
fileset.dig('structural', 'contains').each do |file|
file = StacksFile.new(id: druid, file_name: file['filename'])
yield file
end
end
end

def files_from_xml(druid)
doc = Nokogiri::XML.parse(public_xml(druid))

doc.xpath('//contentMetadata/resource').each do |resource|
Expand Down Expand Up @@ -56,6 +82,10 @@ def public_xml_url(druid)
Settings.purl.url + "#{druid}.xml"
end

def public_json_url(druid)
"#{Settings.purl.url}#{druid}.json"
end

def logger
Rails.logger
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/stacks_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ def druid_parts
def stacks_rights
@stacks_rights ||= StacksRights.new(id:, file_name:)
end
delegate :rights, to: :stacks_rights
delegate :rights, :cocina_rights, to: :stacks_rights
end
2 changes: 1 addition & 1 deletion app/models/stacks_image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ def info_service
def stacks_rights
@stacks_rights ||= StacksRights.new(id:, file_name:)
end
delegate :rights, :maybe_downloadable?, :object_thumbnail?,
delegate :rights, :cocina_rights, :maybe_downloadable?, :object_thumbnail?,
:stanford_restricted?, :restricted_by_location?, :cdl_restricted?, to: :stacks_rights
end
3 changes: 2 additions & 1 deletion app/models/stacks_media_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def file
def stacks_rights
@stacks_rights ||= StacksRights.new(id:, file_name:)
end
delegate :rights, :restricted_by_location?, :stanford_restricted?, :embargoed?,

delegate :rights, :cocina_rights, :restricted_by_location?, :stanford_restricted?, :embargoed?,
:embargo_release_date, to: :stacks_rights
end
Loading