From 85153241fccf0c06e3084b650acfd422027bb608 Mon Sep 17 00:00:00 2001 From: Chris Beer <cabeer@stanford.edu> Date: Wed, 16 Sep 2020 10:00:01 -0700 Subject: [PATCH] Extract configuration to control which IIIF auth services are used (and the different types of generic interactions) --- __tests__/src/selectors/canvases.test.js | 167 ++++++++++------------- src/config/settings.js | 8 ++ src/state/sagas/iiif.js | 2 +- src/state/selectors/canvases.js | 102 +++++--------- 4 files changed, 109 insertions(+), 170 deletions(-) diff --git a/__tests__/src/selectors/canvases.test.js b/__tests__/src/selectors/canvases.test.js index 50cd4530b..b318e19ed 100644 --- a/__tests__/src/selectors/canvases.test.js +++ b/__tests__/src/selectors/canvases.test.js @@ -2,6 +2,7 @@ import manifestFixture001 from '../../fixtures/version-2/001.json'; import manifestFixture019 from '../../fixtures/version-2/019.json'; import minimumRequired from '../../fixtures/version-2/minimumRequired.json'; import minimumRequired3 from '../../fixtures/version-3/minimumRequired.json'; +import settings from '../../../src/config/settings'; import { getVisibleCanvases, @@ -10,10 +11,8 @@ import { getCanvas, getCanvasLabel, selectCanvasAuthService, - selectNextAuthService, selectInfoResponse, getVisibleCanvasNonTiledResources, - selectLogoutAuthService, getVisibleCanvasIds, } from '../../../src/state/selectors/canvases'; @@ -251,69 +250,6 @@ describe('getCanvasLabel', () => { }); }); -describe('selectNextAuthService', () => { - const auth = {}; - const resource = { - service: [ - { - '@id': 'external', - profile: 'http://iiif.io/api/auth/1/external', - }, - { - '@id': 'kiosk', - profile: 'http://iiif.io/api/auth/1/kiosk', - }, - { - '@id': 'clickthrough', - profile: 'http://iiif.io/api/auth/1/clickthrough', - }, - { - '@id': 'login', - profile: 'http://iiif.io/api/auth/1/login', - }, - { - '@id': 'login2', - profile: 'http://iiif.io/api/auth/1/login', - }, - ], - }; - - const noAuthResource = {}; - - it('returns external first', () => { - expect(selectNextAuthService({ auth }, resource).id).toEqual('external'); - }); - - it('returns kiosk next', () => { - auth.external = { isFetching: false, ok: false }; - expect(selectNextAuthService({ auth }, resource).id).toEqual('kiosk'); - }); - - it('returns clickthrough next', () => { - auth.external = { isFetching: false, ok: false }; - auth.kiosk = { isFetching: false, ok: false }; - expect(selectNextAuthService({ auth }, resource).id).toEqual('clickthrough'); - }); - - it('returns logins last', () => { - auth.external = { isFetching: false, ok: false }; - auth.kiosk = { isFetching: false, ok: false }; - auth.clickthrough = { isFetching: false, ok: false }; - expect(selectNextAuthService({ auth }, resource).id).toEqual('login'); - auth.login = { isFetching: false, ok: false }; - expect(selectNextAuthService({ auth }, resource).id).toEqual('login2'); - }); - - it('returns null if there are no services', () => { - expect(selectNextAuthService({ auth }, noAuthResource)).toBeNull(); - }); - - it('returns null if a service is currently in-flight', () => { - auth.external = { isFetching: true }; - expect(selectNextAuthService({ auth }, resource)).toBeNull(); - }); -}); - describe('selectCanvasAuthService', () => { const resource = { service: [ @@ -338,6 +274,7 @@ describe('selectCanvasAuthService', () => { const state = { auth: {}, + config: { auth: settings.auth }, infoResponses: { 'https://iiif.bodleian.ox.ac.uk/iiif/image/9cca8fdd-4a61-4429-8ac1-f648764b4d6d': { json: resource, @@ -357,7 +294,7 @@ describe('selectCanvasAuthService', () => { }; it('returns undefined if there is no current canvas', () => { - expect(selectCanvasAuthService({ manifests: {} }, { manifestId: 'a' })).toBeUndefined(); + expect(selectCanvasAuthService({ config: { auth: settings.auth }, manifests: {} }, { manifestId: 'a' })).toBeUndefined(); }); it('returns the next auth service to try', () => { @@ -378,34 +315,37 @@ describe('selectCanvasAuthService', () => { expect(selectCanvasAuthService({ ...state, auth }, { canvasId: 'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json', manifestId: 'b' }).id).toEqual('external'); expect(selectCanvasAuthService({ ...state, auth }, { canvasId: 'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1', manifestId: 'b' })).toBeUndefined(); }); -}); -describe('selectLogoutAuthService', () => { - it('returns a logout auth service if one exists', () => { - const logout = { - '@id': 'http://foo/logout', - profile: 'http://iiif.io/api/auth/1/logout', - }; - const resource = { - service: [ - { - '@id': 'login', - profile: 'http://iiif.io/api/auth/1/login', - service: [ - logout, - ], - }, - ], - }; - const state = { - auth: { - login: { - ok: true, - }, - }, + describe('proscribed order', () => { + let auth = {}; + const orderedState = { + config: { auth: settings.auth }, infoResponses: { 'https://iiif.bodleian.ox.ac.uk/iiif/image/9cca8fdd-4a61-4429-8ac1-f648764b4d6d': { - json: resource, + json: { + service: [ + { + '@id': 'external', + profile: 'http://iiif.io/api/auth/1/external', + }, + { + '@id': 'kiosk', + profile: 'http://iiif.io/api/auth/1/kiosk', + }, + { + '@id': 'clickthrough', + profile: 'http://iiif.io/api/auth/1/clickthrough', + }, + { + '@id': 'login', + profile: 'http://iiif.io/api/auth/1/login', + }, + { + '@id': 'login2', + profile: 'http://iiif.io/api/auth/1/login', + }, + ], + }, }, }, manifests: { @@ -414,13 +354,43 @@ describe('selectLogoutAuthService', () => { }, }, }; - expect( - selectLogoutAuthService( - state, - { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }, - ).id, - ) - .toBe(logout['@id']); + + it('returns external first', () => { + auth = {}; + expect(selectCanvasAuthService({ ...orderedState, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('external'); + }); + + it('returns kiosk next', () => { + auth = { external: { isFetching: false, ok: false } }; + expect(selectCanvasAuthService({ ...orderedState, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('kiosk'); + }); + + it('returns clickthrough next', () => { + auth = { + external: { isFetching: false, ok: false }, + kiosk: { isFetching: false, ok: false }, + }; + expect(selectCanvasAuthService({ ...orderedState, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('clickthrough'); + }); + + it('returns logins last', () => { + auth = { + clickthrough: { isFetching: false, ok: false }, + external: { isFetching: false, ok: false }, + kiosk: { isFetching: false, ok: false }, + }; + expect(selectCanvasAuthService({ ...orderedState, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('login'); + }); + + it('returns services within a given type using the order from the manifest', () => { + auth = { + clickthrough: { isFetching: false, ok: false }, + external: { isFetching: false, ok: false }, + kiosk: { isFetching: false, ok: false }, + login: { isFetching: false, ok: false }, + }; + expect(selectCanvasAuthService({ ...orderedState, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('login2'); + }); }); }); @@ -430,6 +400,7 @@ describe('selectInfoResponse', () => { const state = { auth: {}, + config: { auth: settings.auth }, infoResponses: { 'https://iiif.bodleian.ox.ac.uk/iiif/image/9cca8fdd-4a61-4429-8ac1-f648764b4d6d': { json: resource, diff --git a/src/config/settings.js b/src/config/settings.js index a13a0b154..0c40de205 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -337,5 +337,13 @@ export default { viewers: true, windows: true, workspace: true, + }, + auth: { + serviceProfiles: [ + { profile: 'http://iiif.io/api/auth/1/external', external: true }, + { profile: 'http://iiif.io/api/auth/1/kiosk', kiosk: true }, + { profile: 'http://iiif.io/api/auth/1/clickthrough' }, + { profile: 'http://iiif.io/api/auth/1/login' }, + ] } }; diff --git a/src/state/sagas/iiif.js b/src/state/sagas/iiif.js index ee1f8e205..e8d44578d 100644 --- a/src/state/sagas/iiif.js +++ b/src/state/sagas/iiif.js @@ -124,7 +124,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'); - const token = accessTokens[accessTokenService.id]; + const token = accessTokenService && accessTokens[accessTokenService.id]; if (token && token.json) return token; } diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index d2083c184..eefaea802 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -5,6 +5,8 @@ import CanvasGroupings from '../../lib/CanvasGroupings'; import MiradorCanvas from '../../lib/MiradorCanvas'; import { miradorSlice } from './utils'; import { getWindow } from './getters'; +import { getConfig } from './config'; +import { getAuth } from './auth'; import { getSequence } from './sequences'; import { getWindowViewType } from './windows'; @@ -207,84 +209,42 @@ export const selectInfoResponse = createSelector( }, ); -const authServiceProfiles = { - clickthrough: true, external: true, kiosk: true, login: true, -}; -/** - * - */ -export function selectNextAuthService({ auth }, resource, filter = authServiceProfiles) { - const orderedAuthServiceProfiles = [ - 'http://iiif.io/api/auth/1/external', - 'http://iiif.io/api/auth/1/kiosk', - 'http://iiif.io/api/auth/1/clickthrough', - 'http://iiif.io/api/auth/1/login', - ]; - - const mapFilterToProfiles = { - 'http://iiif.io/api/auth/1/clickthrough': 'clickthrough', - 'http://iiif.io/api/auth/1/external': 'external', - 'http://iiif.io/api/auth/1/kiosk': 'kiosk', - 'http://iiif.io/api/auth/1/login': 'login', - }; - - for (const profile of orderedAuthServiceProfiles) { - const services = getServices(resource, profile); - for (const service of services) { - if (!auth[service.id]) { - return filter[mapFilterToProfiles[profile]] && service; - } +export const selectCanvasAuthService = createSelector( + [ + selectInfoResponse, + getCanvas, + getConfig, + getAuth, + ], + (infoResponse, canvas, { auth: { serviceProfiles = [] } }, auth) => { + let iiifResource; + iiifResource = infoResponse && infoResponse.json && { ...infoResponse.json, options: {} }; + + if (!iiifResource && canvas) { + const miradorCanvas = new MiradorCanvas(canvas); + const [image] = miradorCanvas.iiifImageResources; - if (auth[service.id].isFetching || auth[service.id].ok) return null; + iiifResource = image && image.getServices()[0]; } - } - return null; -} + if (!iiifResource) return undefined; -/** */ -export function selectActiveAuthService(state, resource) { - const orderedAuthServiceProfiles = [ - 'http://iiif.io/api/auth/1/login', - 'http://iiif.io/api/auth/1/clickthrough', - 'http://iiif.io/api/auth/1/kiosk', - 'http://iiif.io/api/auth/1/external', - ]; - - for (const profile of orderedAuthServiceProfiles) { - const services = getServices(resource, profile); - const service = services.find(s => selectAuthStatus(state, s)); - if (service) return service; - } - - return null; -} + const orderedAuthServiceProfiles = serviceProfiles.map(p => p.profile); -export const selectCanvasAuthService = createSelector( - [ - selectInfoResponse, - state => state, - ], - (infoResponse, state) => { - const resource = infoResponse && infoResponse.json; + let lastAttemptedService; - if (!resource) return undefined; + for (const profile of orderedAuthServiceProfiles) { + const services = getServices(iiifResource, profile); + for (const service of services) { + if (!auth[service.id]) return service; - return selectNextAuthService(state, resource) - || selectActiveAuthService(state, resource); - }, -); + lastAttemptedService = service; -export const selectLogoutAuthService = createSelector( - [ - selectInfoResponse, - state => state, - ], - (infoResponse, state) => { - if (!infoResponse) return undefined; - const authService = selectActiveAuthService(state, infoResponse.json); - if (!authService) return undefined; - return authService.getService('http://iiif.io/api/auth/1/logout'); + if (auth[service.id].isFetching || auth[service.id].ok) return service; + } + } + + return lastAttemptedService; }, ); @@ -299,7 +259,7 @@ export function selectAuthStatus({ auth }, service) { /** Get all the services that match a profile */ function getServices(resource, profile) { - const services = Utils.getServices({ ...resource, options: {} }); + const services = Utils.getServices(resource); return services.filter(service => service.getProfile() === profile); } -- GitLab