Skip to content

Commit

Permalink
Support embargo response from the media auth service
Browse files Browse the repository at this point in the history
So that the media viewer can display an appropriate message to the user
  • Loading branch information
jcoyne committed Nov 3, 2023
1 parent 42ddfdb commit 35272bf
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 104 deletions.
2 changes: 1 addition & 1 deletion app/models/stacks_media_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ def file
def stacks_rights
@stacks_rights ||= StacksRights.new(id: id, file_name: file_name)
end
delegate :rights, :restricted_by_location?, :stanford_restricted?, to: :stacks_rights
delegate :rights, :restricted_by_location?, :stanford_restricted?, :embargoed?, to: :stacks_rights
end
2 changes: 2 additions & 0 deletions app/models/stacks_rights.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def restricted_by_location?
rights.restricted_by_location?(file_name)
end

delegate :embargoed?, to: :rights

def object_thumbnail?
doc = Nokogiri::XML.parse(public_xml)

Expand Down
88 changes: 50 additions & 38 deletions app/services/media_authentication_json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,75 @@
# A class to model various authentication checks on media objects
# and return a hash to be used as JSON in a controller response
class MediaAuthenticationJson
# @param [User] user
# @param [StacksMediaStream] media
# @param [String] auth_url the login url to send to the client if the user could login.
# @param [Ability] ability the CanCan ability object
def initialize(user:, media:, auth_url:, ability:)
@user = user
@media = media
@auth_url = auth_url
@ability = ability
end

def as_json(*)
return stanford_or_location_response if location_grants_access? && stanford_grants_access?
return location_only_response if location_grants_access?
return stanford_only_response if stanford_grants_access?
class DenyResponse
def initialize(auth_url)
@auth_url = auth_url
@status = []
end

{}
end
attr_reader :status, :auth_url

private
def as_json
return { status: }.compact_blank.merge(login_service) if stanford_restricted?

attr_reader :auth_url, :media, :user, :ability
{ status: }.compact_blank
end

def location_only_response
{
status: [:location_restricted]
}
end
def login_service
{
service: {
'@id' => auth_url,
'label' => 'Stanford-affiliated? Login to play'
}
}
end

def stanford_only_response
{
status: [:stanford_restricted]
}.merge(login_service)
end
def stanford_restricted?
status.include?(:stanford_restricted)
end

def stanford_or_location_response
{
status: [
:stanford_restricted,
:location_restricted
]
}.merge(login_service)
end
def add_stanford_restricted!
status << :stanford_restricted
end

def login_service
{
service: {
'@id' => auth_url,
'label' => 'Stanford-affiliated? Login to play'
}
}
def add_location_restricted!
status << :location_restricted
end

def add_embargo!
status << :embargoed
end
end

def stanford_restricted?
media.stanford_restricted?
def build_response
DenyResponse.new(auth_url).tap do |response|
response.add_stanford_restricted! if stanford_grants_access?
response.add_location_restricted! if location_grants_access?
response.add_embargo! if embargoed?
end
end

def location_restricted?
media.restricted_by_location?
def as_json(*)
build_response.as_json
end

private

attr_reader :auth_url, :media, :user, :ability

delegate :embargoed?, :stanford_restricted?, :restricted_by_location?, to: :media

def user_is_in_location?
ability.can? :access, media
end
Expand All @@ -70,6 +82,6 @@ def stanford_grants_access?
end

def location_grants_access?
location_restricted? && !user_is_in_location?
restricted_by_location? && !user_is_in_location?
end
end
158 changes: 96 additions & 62 deletions spec/requests/media_auth_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,115 @@
require 'rails_helper'

RSpec.describe "Authentication for Media requests", type: :request do

let(:user_no_loc_no_webauth) { User.new }
let(:user_webauth_stanford_no_loc) { User.new(webauth_user: true, ldap_groups: %w(stanford:stanford)) }
let(:druid) { 'bb582xs1304' }

describe "#auth_check" do
let(:format) { 'mp4' }
let(:public_xml) do
<<-XML
<publicObject>
#{rights_xml}
</publicObject>
XML
let(:format) { 'mp4' }
let(:public_xml) do
<<-XML
<publicObject>
#{rights_xml}
</publicObject>
XML
end

let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<group>Stanford</group>
</machine>
</access>
</rightsMetadata>
EOF
end

let(:mock_media) do
sms = StacksMediaStream.new(id: 'bb582xs1304', file_name: 'file', format: format)
allow(Purl).to receive(:public_xml).with('bb582xs1304').and_return(public_xml)
sms
end

before do
allow_any_instance_of(MediaController).to receive(:current_user).and_return(user)
allow_any_instance_of(MediaController).to receive(:current_media).and_return(mock_media)
end

context 'when the user is stanford authenticated' do
let(:user) { User.new(webauth_user: true, ldap_groups: %w(stanford:stanford)) }

it 'gets the success JSON and a token' do
get "/media/#{druid}/file.#{format}/auth_check"
expect(response.parsed_body['status']).to eq 'success'
expect(response.parsed_body['token']).to match(/^[%a-zA-Z0-9]+/)
end
end

context 'when the user is not authenticated' do
let(:user) { User.new }

let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<group>Stanford</group>
</machine>
</access>
</rightsMetadata>
EOF
context 'stanford restricted' do
it 'indicates that the object is restricted in the json' do
get "/media/#{druid}/file.#{format}/auth_check"
expect(response.parsed_body['status']).to eq ['stanford_restricted']
end
end

let(:mock_media) do
sms = StacksMediaStream.new(id: 'bb582xs1304', file_name: 'file', format: format)
allow(Purl).to receive(:public_xml).with('bb582xs1304').and_return(public_xml)
sms
context 'location restricted' do
let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<location>location1</location>
</machine>
</access>
</rightsMetadata>
EOF
end

it 'indicates that the object is location restricted in the json' do
get "/media/#{druid}/file.#{format}/auth_check"
expect(response.parsed_body['status']).to eq ['location_restricted']
end
end

context 'when the user can read/stream the file' do
it 'gets the success JSON and a token' do
allow_any_instance_of(MediaController).to receive(:current_user).and_return(user_webauth_stanford_no_loc)
allow_any_instance_of(MediaController).to receive(:current_media).and_return(mock_media)
get "/media/#{druid}/file.#{format}/auth_check.js"
body = JSON.parse(response.body)
expect(body['status']).to eq 'success'
expect(body['token']).to match(/^[%a-zA-Z0-9]+/)
context 'when the file is embargoed or stanford restricted' do
let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<embargoReleaseDate>2099-05-15</embargoReleaseDate>
<group>stanford</group>
</machine>
</access>
</rightsMetadata>
EOF
end

it 'indicates that the object is stanford restricted and embargoed in the json' do
get "/media/#{druid}/file.#{format}/auth_check"
expect(response.parsed_body['status']).to eq %w[stanford_restricted embargoed]
end
end

context 'when the user cannot read/stream the file' do
context 'stanford restricted' do
it 'indicates that the object is restricted in the json' do
allow_any_instance_of(MediaController).to receive(:current_user).and_return(user_no_loc_no_webauth)
allow_any_instance_of(MediaController).to receive(:current_media).and_return(mock_media)
get "/media/#{druid}/file.#{format}/auth_check.js"
body = JSON.parse(response.body)
expect(body['status']).to eq(['stanford_restricted'])
end
context 'when the file is embargoed' do
let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<embargoReleaseDate>2099-05-15</embargoReleaseDate>
</machine>
</access>
</rightsMetadata>
EOF
end

context 'location restricted' do
let(:rights_xml) do
<<-EOF.strip_heredoc
<rightsMetadata>
<access type="read">
<machine>
<location>location1</location>
</machine>
</access>
</rightsMetadata>
EOF
end

it 'indicates that the object is location restricted in the json' do
allow_any_instance_of(MediaController).to receive(:current_user).and_return(user_no_loc_no_webauth)
allow_any_instance_of(MediaController).to receive(:current_media).and_return(mock_media)
get "/media/#{druid}/file.#{format}/auth_check.js"
body = JSON.parse(response.body)
expect(body['status']).to eq(['location_restricted'])
end
it 'indicates that the object is embargoed in the json' do
get "/media/#{druid}/file.#{format}/auth_check.js"
expect(response.parsed_body['status']).to eq ['embargoed']
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/services/media_authentication_json_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

RSpec.describe MediaAuthenticationJson do
let(:media) do
double(
'Media',
instance_double(
StacksMediaStream,
restricted_by_location?: false,
stanford_restricted?: false,
location_rights: false
embargoed?: false
)
end
let(:ability) { Ability.new(user) }
Expand Down

0 comments on commit 35272bf

Please sign in to comment.