From cf3caf1a0ba3959e5f7aabe4576c112ebc443123 Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Mon, 2 Oct 2023 11:37:48 -0400 Subject: [PATCH 1/8] MiradorCanvas inspects annotation type and service profile to determine iiif images - jest specs for image service identification - jest specs for resource type filters - #3789 --- .../fixtures/version-3/auth2-active.json | 65 +++++++++++++++++ __tests__/fixtures/version-3/auth2-kiosk.json | 69 +++++++++++++++++++ __tests__/src/lib/MiradorCanvas.test.js | 24 +++++++ __tests__/src/lib/typeFilters.test.js | 52 ++++++++++++++ src/lib/MiradorCanvas.js | 23 +++---- src/lib/canvasTypes.js | 37 ++++++++++ src/lib/getServices.js | 44 ++++++++++++ src/lib/typeFilters.js | 45 ++++++++++++ 8 files changed, 346 insertions(+), 13 deletions(-) create mode 100644 __tests__/fixtures/version-3/auth2-active.json create mode 100644 __tests__/fixtures/version-3/auth2-kiosk.json create mode 100644 __tests__/src/lib/typeFilters.test.js create mode 100644 src/lib/canvasTypes.js create mode 100644 src/lib/getServices.js create mode 100644 src/lib/typeFilters.js diff --git a/__tests__/fixtures/version-3/auth2-active.json b/__tests__/fixtures/version-3/auth2-active.json new file mode 100644 index 0000000000..cb459a6909 --- /dev/null +++ b/__tests__/fixtures/version-3/auth2-active.json @@ -0,0 +1,65 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json", + "type": "Manifest", + "label": { + "en": [ + "An actively authorized video" + ] + }, + "items": [ + { + "id": "https://auth.example.org/my-video1", + "type": "Canvas", + "label": { + "en": [ + "Canvas with a single IIIF video" + ] + }, + "height": 3024, + "width": 4032, + "items": [ + { + "id": "https://auth.example.org/my-video/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://auth.example.org/my-video/annotation", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://auth.example.org/my-video.mp4", + "type": "Video", + "service": [ + { + "id": "https://auth.example.org/probe/my-video", + "type": "AuthProbeService2", + "service" : [ + { + "id": "https://auth.example.org/login", + "type": "AuthAccessService2", + "profile": "active", + "label": { "en": [ "Login to Example Institution" ] }, + "service" : [ + { + "id": "https://auth.example.org/token", + "type": "AuthAccessTokenService2" + }, + { + "id": "https://auth.example.org/logout", + "type": "AuthLogoutService2", + "label": { "en": [ "Logout from Example Institution" ] } + } + ] + } + ] + } + ] + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/__tests__/fixtures/version-3/auth2-kiosk.json b/__tests__/fixtures/version-3/auth2-kiosk.json new file mode 100644 index 0000000000..9187902447 --- /dev/null +++ b/__tests__/fixtures/version-3/auth2-kiosk.json @@ -0,0 +1,69 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/manifest.json", + "type": "Manifest", + "label": { + "en": [ + "A kiosk-authorized image" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1", + "type": "Canvas", + "label": { + "en": [ + "Canvas with a single IIIF image" + ] + }, + "height": 3024, + "width": 4032, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/page/p1/1", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0005-image-service/annotation/p0001-image", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/max/0/default.jpg", + "type": "Image", + "format": "image/jpeg", + "height": 3024, + "width": 4032, + "service": [ + { + "id": "https://auth.example.org/probe/918ecd18c2592080851777620de9bcb5-gottingen", + "type": "AuthProbeService2", + "service" : [ + { + "id": "https://auth.example.org/kiosk", + "type": "AuthAccessService2", + "profile": "kiosk", + "label": { "en": [ "Kiosk Login" ] }, + "service" : [ + { + "id": "https://auth.example.org/token/918ecd18c2592080851777620de9bcb5-gottingen", + "type": "AuthAccessTokenService2" + } + ] + } + ] + }, + { + "id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen", + "profile": "level1", + "type": "ImageService3" + } + ] + }, + "target": "https://iiif.io/api/cookbook/recipe/0005-image-service/canvas/p1" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/__tests__/src/lib/MiradorCanvas.test.js b/__tests__/src/lib/MiradorCanvas.test.js index 5502c67746..26f4ce6b00 100644 --- a/__tests__/src/lib/MiradorCanvas.test.js +++ b/__tests__/src/lib/MiradorCanvas.test.js @@ -9,6 +9,8 @@ import fragmentFixtureV3 from '../../fixtures/version-3/hamilton.json'; import audioFixture from '../../fixtures/version-3/0002-mvm-audio.json'; import videoFixture from '../../fixtures/version-3/0015-start.json'; import videoWithAnnoCaptions from '../../fixtures/version-3/video_with_annotation_captions.json'; +import auth2WithImage from '../../fixtures/version-3/auth2-kiosk.json'; +import auth2WithVideo from '../../fixtures/version-3/auth2-active.json'; describe('MiradorCanvas', () => { let instance; @@ -133,4 +135,26 @@ describe('MiradorCanvas', () => { expect(instance.v3VttContent.length).toEqual(1); }); }); + describe('iiifImageResources', () => { + it('returns image resources', () => { + instance = new MiradorCanvas( + Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0], + ); + expect(instance.iiifImageResources.length).toEqual(1); + }); + it('returns only image resources', () => { + instance = new MiradorCanvas( + Utils.parseManifest(auth2WithVideo).getSequences()[0].getCanvases()[0], + ); + expect(instance.iiifImageResources.length).toEqual(0); + }); + }); + describe('imageServiceIds', () => { + it('returns image service IDs', () => { + instance = new MiradorCanvas( + Utils.parseManifest(auth2WithImage).getSequences()[0].getCanvases()[0], + ); + expect(instance.imageServiceIds[0]).toEqual('https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen'); + }); + }); }); diff --git a/__tests__/src/lib/typeFilters.test.js b/__tests__/src/lib/typeFilters.test.js new file mode 100644 index 0000000000..854712d785 --- /dev/null +++ b/__tests__/src/lib/typeFilters.test.js @@ -0,0 +1,52 @@ +import { JSONLDResource } from 'manifesto.js'; +import { v4 as uuid } from 'uuid'; +import { + filterByTypes, audioResourcesFrom, anyImageServices, hasImageService, + iiifImageResourcesFrom, textResourcesFrom, videoResourcesFrom, +} from '../../../src/lib/typeFilters'; + +/** + */ +function resourceFixtureForProps(props) { + return new JSONLDResource({ + id: uuid(), + ...props, + }); +} + +describe('filterByTypes', () => { + it('returns a resource of one type', () => { + const typeFixture = 'someType'; + const resourceFixture = resourceFixtureForProps({ '@type': typeFixture }); + expect(filterByTypes([resourceFixture], typeFixture)).toEqual([resourceFixture]); + }); + it('returns a resource of any given types', () => { + const typeFixture = 'someType'; + const resourceFixture = resourceFixtureForProps({ '@type': typeFixture }); + expect(filterByTypes([resourceFixture], ['anotherType', typeFixture])).toEqual([resourceFixture]); + }); +}); + +describe('audioResourcesFrom', () => { + it('returns a resource of audio type', () => { + const typeFixture = 'Audio'; + const resourceFixture = resourceFixtureForProps({ '@type': typeFixture }); + expect(audioResourcesFrom([resourceFixture])).toEqual([resourceFixture]); + }); +}); + +describe('videoResourcesFrom', () => { + it('returns a resource of audio type', () => { + const typeFixture = 'Video'; + const resourceFixture = resourceFixtureForProps({ '@type': typeFixture }); + expect(videoResourcesFrom([resourceFixture])).toEqual([resourceFixture]); + }); +}); + +describe('textResourcesFrom', () => { + it('returns a resource of audio type', () => { + const typeFixture = 'Document'; + const resourceFixture = resourceFixtureForProps({ '@type': typeFixture }); + expect(textResourcesFrom([resourceFixture])).toEqual([resourceFixture]); + }); +}); diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js index 6e17fad668..64ee20cecd 100644 --- a/src/lib/MiradorCanvas.js +++ b/src/lib/MiradorCanvas.js @@ -1,6 +1,9 @@ import flatten from 'lodash/flatten'; import flattenDeep from 'lodash/flattenDeep'; import { Canvas, AnnotationPage, Annotation } from 'manifesto.js'; +import { audioResourcesFrom, iiifImageResourcesFrom, videoResourcesFrom } from './typeFilters'; +import canvasTypes from './canvasTypes'; + /** * MiradorCanvas - adds additional, testable logic around Manifesto's Canvas * https://iiif-commons.github.io/manifesto/classes/_canvas_.manifesto.canvas.html @@ -64,7 +67,7 @@ export default class MiradorCanvas { return this.imageResources[0]; } - /** */ + /** Despite name, this method returns paintable resources, not just images */ get imageResources() { const resources = flattenDeep([ this.canvas.getImages().map(i => i.getResource()), @@ -83,19 +86,12 @@ export default class MiradorCanvas { /** */ get videoResources() { - const resources = flattenDeep([ - this.canvas.getContent().map(i => i.getBody()), - ]); - return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video')); + return videoResourcesFrom(this.imageResources); } /** */ get audioResources() { - const resources = flattenDeep([ - this.canvas.getContent().map(i => i.getBody()), - ]); - - return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound')); + return audioResourcesFrom(this.imageResources); } /** */ @@ -158,13 +154,14 @@ export default class MiradorCanvas { /** */ get iiifImageResources() { - return this.imageResources - .filter(r => r && r.getServices()[0] && r.getServices()[0].id); + return iiifImageResourcesFrom(this.imageResources); } /** */ get imageServiceIds() { - return this.iiifImageResources.map(r => r.getServices()[0].id); + /** filter services by profile for IIIF images services */ + const imageServiceFilter = s => canvasTypes.imageServiceProfiles.includes(s.getProfile()); + return this.iiifImageResources.map(r => r.getServices().filter(imageServiceFilter)[0].id); } /** diff --git a/src/lib/canvasTypes.js b/src/lib/canvasTypes.js new file mode 100644 index 0000000000..2b5f3b8972 --- /dev/null +++ b/src/lib/canvasTypes.js @@ -0,0 +1,37 @@ +/** values for type/@type that indicate an image content resource */ +const imageTypes = ['Image', 'StillImage', 'dctypes:Image', 'dctypes:StillImage']; +Object.freeze(imageTypes); + +/** values for type/@type that indicate a sound content resource */ +const audioTypes = ['Audio', 'Sound', 'dctypes:Audio', 'dctypes:Sound']; +Object.freeze(audioTypes); + +/** values for type/@type that indicate a text content resource */ +const textTypes = ['Document', 'Text', 'dctypes:Document', 'dctypes:Text']; +Object.freeze(textTypes); + +/** values for type/@type that indicate a video content resource */ +const videoTypes = ['Video', 'MovingImage', 'dctypes:Video', 'dctypes:MovingImage']; +Object.freeze(videoTypes); + +/** values for profile that indicate an image service */ +const imageServiceProfiles = [ + 'level2', + 'level1', + 'level0', + 'http://iiif.io/api/image/2/level2.json', + 'http://iiif.io/api/image/2/level1.json', + 'http://iiif.io/api/image/2/level0.json', +]; + +Object.freeze(imageServiceProfiles); + +const canvasTypes = { + audioTypes, + imageServiceProfiles, + imageTypes, + textTypes, + videoTypes, +}; + +export default canvasTypes; diff --git a/src/lib/getServices.js b/src/lib/getServices.js new file mode 100644 index 0000000000..3abf75d8fb --- /dev/null +++ b/src/lib/getServices.js @@ -0,0 +1,44 @@ +import { Utils } from 'manifesto.js'; +import { filterByTypes } from './typeFilters'; + +/** + */ +export function anyAuthServices(resource) { + return resource + && Utils.getServices(resource).filter(s => (s.getProfile() + && s.getProfile().match(/http:\/\/iiif.io\/api\/auth\//)) + || (s.getProperty('type') + && s.getProperty('type').match(/^Auth.*2$/))); +} + +/** + */ +export function getProbeService(resource) { + return resource + && ( + Utils.getService(resource, 'http://iiif.io/api/auth/1/probe') + || filterByTypes(Utils.getServices(resource), 'AuthProbeService2')[0] + ); +} + +/** + */ +export function getTokenService(resource) { + return resource + && ( + Utils.getService(resource, 'http://iiif.io/api/auth/1/token') + || Utils.getService(resource, 'http://iiif.io/api/auth/0/token') + || filterByTypes(Utils.getServices(resource), 'AuthAccessTokenService2')[0] + ); +} + +/** + */ +export function getLogoutService(resource) { + return resource + && ( + Utils.getService(resource, 'http://iiif.io/api/auth/1/logout') + || Utils.getService(resource, 'http://iiif.io/api/auth/0/logout') + || filterByTypes(Utils.getServices(resource), 'AuthLogoutService2')[0] + ); +} diff --git a/src/lib/typeFilters.js b/src/lib/typeFilters.js new file mode 100644 index 0000000000..5a7a70ea27 --- /dev/null +++ b/src/lib/typeFilters.js @@ -0,0 +1,45 @@ +import canvasTypes from './canvasTypes'; +/** + */ +export function filterByTypes(resources, types) { + if (types === undefined || resources === undefined) return []; + + if (!Array.isArray(types)) { + return resources.filter((resource) => types === resource.getProperty('type')); + } + + return resources.filter((resource) => types.includes(resource.getProperty('type'))); +} + +/** */ +export function audioResourcesFrom(resources) { + return filterByTypes(resources, canvasTypes.audioTypes); +} + +/** + */ +export function anyImageServices(resource) { + const services = resource ? resource.getServices() : []; + return services.filter(s => canvasTypes.imageServiceProfiles.includes(s.getProfile())); +} + +/** */ +export function hasImageService(resource) { + const imageServices = anyImageServices(resource); + return imageServices[0] && imageServices[0].id; +} + +/** */ +export function iiifImageResourcesFrom(resources) { + return filterByTypes(resources, canvasTypes.imageTypes).filter((r) => hasImageService(r)); +} + +/** */ +export function textResourcesFrom(resources) { + return filterByTypes(resources, canvasTypes.textTypes); +} + +/** */ +export function videoResourcesFrom(resources) { + return filterByTypes(resources, canvasTypes.videoTypes); +} From 2ca78c728b987c7552acd9618e706df0375df2c9 Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Mon, 2 Oct 2023 13:31:19 -0400 Subject: [PATCH 2/8] add IIIF Auth2 authentication profiles to settings (#3789) --- src/config/settings.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/settings.js b/src/config/settings.js index 5aa08c8f99..079ee0a598 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -374,7 +374,10 @@ export default { { profile: 'http://iiif.io/api/auth/0/external', external: true }, { profile: 'http://iiif.io/api/auth/0/kiosk', kiosk: true }, { profile: 'http://iiif.io/api/auth/0/clickthrough' }, - { profile: 'http://iiif.io/api/auth/0/login' } + { profile: 'http://iiif.io/api/auth/0/login' }, + { profile: 'external', external: true }, + { profile: 'kiosk', kiosk: true }, + { profile: 'active' } ] } }; From 9262a070a3e880b954ae9351e8385072952e3447 Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Mon, 2 Oct 2023 13:32:01 -0400 Subject: [PATCH 3/8] auth state selector should check non-image resources - IIIF Auth2 services will be members of a probe service - #3789 --- __tests__/src/selectors/auth.test.js | 11 +++++++++++ src/state/selectors/auth.js | 19 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/__tests__/src/selectors/auth.test.js b/__tests__/src/selectors/auth.test.js index 2071e99317..b06c3f740f 100644 --- a/__tests__/src/selectors/auth.test.js +++ b/__tests__/src/selectors/auth.test.js @@ -1,5 +1,6 @@ import manifestFixture001 from '../../fixtures/version-2/001.json'; import manifestFixture019 from '../../fixtures/version-2/019.json'; +import manifestFixtureAuth2ActiveVideo from '../../fixtures/version-3/auth2-active.json'; import settings from '../../../src/config/settings'; import { getAccessTokens, @@ -61,6 +62,9 @@ describe('selectCurrentAuthServices', () => { b: { json: manifestFixture019, }, + c: { + json: manifestFixtureAuth2ActiveVideo, + }, }, windows: { noCanvas: { @@ -84,6 +88,12 @@ describe('selectCurrentAuthServices', () => { 'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1', ], }, + z: { + manifestId: 'c', + visibleCanvases: [ + 'https://auth.example.org/my-video1', + ], + }, }, }; @@ -93,6 +103,7 @@ describe('selectCurrentAuthServices', () => { it('returns the next auth service to try', () => { expect(selectCurrentAuthServices(state, { windowId: 'w' })[0].id).toEqual('external'); + expect(selectCurrentAuthServices(state, { windowId: 'z' })[0].id).toEqual('https://auth.example.org/login'); }); it('returns the service if the next auth service is interactive', () => { diff --git a/src/state/selectors/auth.js b/src/state/selectors/auth.js index 7c290bfca4..d38ab255b3 100644 --- a/src/state/selectors/auth.js +++ b/src/state/selectors/auth.js @@ -1,6 +1,9 @@ import { createSelector } from 'reselect'; import { Utils } from 'manifesto.js'; import flatten from 'lodash/flatten'; +import { + audioResourcesFrom, filterByTypes, textResourcesFrom, videoResourcesFrom, +} from '../../lib/typeFilters'; import MiradorCanvas from '../../lib/MiradorCanvas'; import { miradorSlice } from './utils'; import { getConfig } from './config'; @@ -48,15 +51,27 @@ export const selectCurrentAuthServices = createSelector( })); } + if (currentAuthResources.length === 0 && canvases) { + currentAuthResources = flatten(canvases.map(c => { + const miradorCanvas = new MiradorCanvas(c); + const canvasResources = miradorCanvas.imageResources; + return videoResourcesFrom(canvasResources) + .concat(audioResourcesFrom(canvasResources)) + .concat(textResourcesFrom(canvasResources)); + })); + } + if (!currentAuthResources) return []; if (currentAuthResources.length === 0) return []; const currentAuthServices = currentAuthResources.map(resource => { let lastAttemptedService; - const services = Utils.getServices(resource); + const resourceServices = Utils.getServices(resource); + const probeServices = filterByTypes(resourceServices, 'AuthProbeService2'); + const probeServiceServices = flatten(probeServices.map(p => Utils.getServices(p))); for (const authProfile of serviceProfiles) { - const profiledAuthServices = services.filter( + const profiledAuthServices = resourceServices.concat(probeServiceServices).filter( p => authProfile.profile === p.getProfile(), ); From 45a42fed4398e9be251b4a003849f83944637c9c Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Mon, 2 Oct 2023 14:57:59 -0400 Subject: [PATCH 4/8] look for typed services providing Auth2 token/logout (#3789) - DRY search routines into functions defined in getServices - all routines should look for Auth2 types and Auth1 profiles - include search for uncoducmented Auth1 implementation of explicit probe service --- __tests__/src/lib/getServices.test.js | 90 +++++++++++++++++++++++++++ src/containers/IIIFAuthentication.js | 12 +--- src/state/sagas/auth.js | 10 ++- src/state/sagas/iiif.js | 4 +- 4 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 __tests__/src/lib/getServices.test.js diff --git a/__tests__/src/lib/getServices.test.js b/__tests__/src/lib/getServices.test.js new file mode 100644 index 0000000000..d7f4a15e99 --- /dev/null +++ b/__tests__/src/lib/getServices.test.js @@ -0,0 +1,90 @@ +import { v4 as uuid } from 'uuid'; +import { + anyAuthServices, getLogoutService, getProbeService, getTokenService, +} from '../../../src/lib/getServices'; + +/** + */ +function resourceFixtureWithService(props) { + return { + id: uuid(), + services: [ + { ...props }, + ], + type: 'Dataset', + }; +} + +/** + */ +function actualLogoutServiceId(resource) { + const service = getLogoutService(resource); + return service + && service.id; +} + +/** + */ +function actualTokenServiceId(resource) { + const service = getTokenService(resource); + return service + && service.id; +} + +/** + */ +function actualProbeServiceId(resource) { + const service = getProbeService(resource); + return service + && service.id; +} + +describe('anyAuthServices', () => { + it('returns a filtered list', () => { + const serviceId = uuid(); + const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/anyService' }); + expect(anyAuthServices(auth0).length).toEqual(1); + const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/anyService' }); + expect(anyAuthServices(auth1).length).toEqual(1); + const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthAnyService2' }); + expect(anyAuthServices(auth2).length).toEqual(1); + const notAuthProfile = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/not-auth/1/anyService' }); + expect(anyAuthServices(notAuthProfile).length).toEqual(0); + const notAuthType = resourceFixtureWithService({ id: serviceId, type: 'NotAuthAnyService2' }); + expect(anyAuthServices(notAuthType).length).toEqual(0); + }); +}); + +describe('getLogoutService', () => { + it('returns a Service', () => { + const serviceId = uuid(); + const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/logout' }); + expect(actualLogoutServiceId(auth0)).toEqual(serviceId); + const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/logout' }); + expect(actualLogoutServiceId(auth1)).toEqual(serviceId); + const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthLogoutService2' }); + expect(actualLogoutServiceId(auth2)).toEqual(serviceId); + }); +}); + +describe('getProbeService', () => { + it('returns a Service', () => { + const serviceId = uuid(); + const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/probe' }); + expect(actualProbeServiceId(auth1)).toEqual(serviceId); + const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthProbeService2' }); + expect(actualProbeServiceId(auth2)).toEqual(serviceId); + }); +}); + +describe('getTokenService', () => { + it('returns a Service', () => { + const serviceId = uuid(); + const auth0 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/0/token' }); + expect(actualTokenServiceId(auth0)).toEqual(serviceId); + const auth1 = resourceFixtureWithService({ id: serviceId, profile: 'http://iiif.io/api/auth/1/token' }); + expect(actualTokenServiceId(auth1)).toEqual(serviceId); + const auth2 = resourceFixtureWithService({ id: serviceId, type: 'AuthAccessTokenService2' }); + expect(actualTokenServiceId(auth2)).toEqual(serviceId); + }); +}); diff --git a/src/containers/IIIFAuthentication.js b/src/containers/IIIFAuthentication.js index 7d51087168..bfeb27461f 100644 --- a/src/containers/IIIFAuthentication.js +++ b/src/containers/IIIFAuthentication.js @@ -1,8 +1,8 @@ import { connect } from 'react-redux'; import { compose } from 'redux'; import { withTranslation } from 'react-i18next'; -import { Utils } from 'manifesto.js'; import { withPlugins } from '../extend/withPlugins'; +import { getLogoutService, getTokenService } from '../lib/getServices'; import * as actions from '../state/actions'; import { getAuth, @@ -23,14 +23,8 @@ const mapStateToProps = (state, { windowId }) => { // TODO: get the most actionable auth service... const service = services[0]; - const accessTokenService = service && ( - Utils.getService(service, 'http://iiif.io/api/auth/1/token') - || Utils.getService(service, 'http://iiif.io/api/auth/0/token') - ); - const logoutService = service && ( - Utils.getService(service, 'http://iiif.io/api/auth/1/logout') - || Utils.getService(service, 'http://iiif.io/api/auth/0/logout') - ); + const accessTokenService = getTokenService(service); + const logoutService = getLogoutService(service); const authStatuses = getAuth(state); const authStatus = service && authStatuses[service.id]; diff --git a/src/state/sagas/auth.js b/src/state/sagas/auth.js index 732a482368..7cd2eafd37 100644 --- a/src/state/sagas/auth.js +++ b/src/state/sagas/auth.js @@ -5,6 +5,7 @@ import { Utils } from 'manifesto.js'; import flatten from 'lodash/flatten'; import ActionTypes from '../actions/action-types'; import MiradorCanvas from '../../lib/MiradorCanvas'; +import { getTokenService } from '../../lib/getServices'; import { addAuthenticationRequest, resolveAuthenticationRequest, @@ -52,8 +53,7 @@ export function* refetchInfoResponses({ serviceId }) { const haveThisTokenService = infoResponse => { const services = Utils.getServices(infoResponse); return services.some(e => { - const infoTokenService = Utils.getService(e, 'http://iiif.io/api/auth/1/token') - || Utils.getService(e, 'http://iiif.io/api/auth/0/token'); + const infoTokenService = getTokenService(e); return infoTokenService && infoTokenService.id === serviceId; }); }; @@ -90,8 +90,7 @@ export function* doAuthWorkflow({ infoJson, windowId }) { // start the auth yield put(addAuthenticationRequest(windowId, authService.id, authService.getProfile())); } else if (profileConfig.external) { - const tokenService = Utils.getService(authService, 'http://iiif.io/api/auth/1/token') - || Utils.getService(authService, 'http://iiif.io/api/auth/0/token'); + const tokenService = getTokenService(authService); if (!tokenService) return; // resolve the auth @@ -107,8 +106,7 @@ export function* rerequestOnAccessTokenFailure({ infoJson, windowId, tokenServic // make sure we have an auth service to try const authService = Utils.getServices(infoJson).find(service => { - const tokenService = Utils.getService(service, 'http://iiif.io/api/auth/1/token') - || Utils.getService(service, 'http://iiif.io/api/auth/0/token'); + const tokenService = getTokenService(service); return tokenService && tokenService.id === tokenServiceId; }); diff --git a/src/state/sagas/iiif.js b/src/state/sagas/iiif.js index 3a613ee934..d7a1f7115d 100644 --- a/src/state/sagas/iiif.js +++ b/src/state/sagas/iiif.js @@ -10,6 +10,7 @@ import { receiveSearch, receiveSearchFailure, receiveAnnotation, receiveAnnotationFailure, } from '../actions'; +import { getTokenService } from '../../lib/getServices'; import { getManifests, getRequestsConfig, @@ -128,8 +129,7 @@ function* getAccessTokenService(resource) { for (let i = 0; i < services.length; i += 1) { const authService = services[i]; - const accessTokenService = Utils.getService(authService, 'http://iiif.io/api/auth/1/token') - || Utils.getService(authService, 'http://iiif.io/api/auth/0/token'); + const accessTokenService = getTokenService(authService); const token = accessTokenService && accessTokens[accessTokenService.id]; if (token && token.json) return token; } From fc73784a54ecf36ec60de1a508b788d13e8b5c78 Mon Sep 17 00:00:00 2001 From: Benjamin Armintor Date: Wed, 25 Oct 2023 15:05:16 -0400 Subject: [PATCH 5/8] VideoViewer component skips resources without an id attribute --- src/components/VideoViewer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js index e8a3d617df..bf9ef76779 100644 --- a/src/components/VideoViewer.js +++ b/src/components/VideoViewer.js @@ -9,15 +9,16 @@ export class VideoViewer extends Component { const { captions, classes, videoOptions, videoResources, } = this.props; + return (