diff --git a/__tests__/src/selectors/auth.test.js b/__tests__/src/selectors/auth.test.js index 698b32115d1c2f8986e10b482660c19dcef7624f..2071e993177a68d8b536f9aade9d58971914c2d3 100644 --- a/__tests__/src/selectors/auth.test.js +++ b/__tests__/src/selectors/auth.test.js @@ -1,5 +1,9 @@ +import manifestFixture001 from '../../fixtures/version-2/001.json'; +import manifestFixture019 from '../../fixtures/version-2/019.json'; +import settings from '../../../src/config/settings'; import { getAccessTokens, + selectCurrentAuthServices, } from '../../../src/state/selectors/auth'; describe('getAccessTokens', () => { @@ -16,3 +20,178 @@ describe('getAccessTokens', () => { expect(accessTokens).toEqual(state.accessTokens); }); }); + +describe('selectCurrentAuthServices', () => { + const resource = { + service: [ + { + '@id': 'external', + profile: 'http://iiif.io/api/auth/1/external', + }, + { + '@id': 'login', + profile: 'http://iiif.io/api/auth/1/login', + }, + ], + }; + const externalOnly = { + service: [ + { + '@id': 'external', + profile: 'http://iiif.io/api/auth/1/external', + }, + ], + }; + + const state = { + auth: {}, + config: { auth: settings.auth }, + infoResponses: { + 'https://iiif.bodleian.ox.ac.uk/iiif/image/9cca8fdd-4a61-4429-8ac1-f648764b4d6d': { + json: resource, + }, + 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44': { + json: externalOnly, + }, + }, + manifests: { + a: { + json: manifestFixture001, + }, + b: { + json: manifestFixture019, + }, + }, + windows: { + noCanvas: { + manifestId: 'a', + }, + w: { + manifestId: 'a', + visibleCanvases: [ + 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', + ], + }, + x: { + manifestId: 'b', + visibleCanvases: [ + 'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json', + ], + }, + y: { + manifestId: 'b', + visibleCanvases: [ + 'https://purl.stanford.edu/fr426cg9537/iiif/canvas/fr426cg9537_1', + ], + }, + }, + }; + + it('returns undefined if there is no current canvas', () => { + expect(selectCurrentAuthServices({ config: { auth: settings.auth }, manifests: {} }, { windowId: 'noCanvas' })[0]).toBeUndefined(); + }); + + it('returns the next auth service to try', () => { + expect(selectCurrentAuthServices(state, { windowId: 'w' })[0].id).toEqual('external'); + }); + + it('returns the service if the next auth service is interactive', () => { + const auth = { external: { isFetching: false, ok: false } }; + expect(selectCurrentAuthServices({ ...state, auth }, { windowId: 'w' })[0].id).toEqual('login'); + }); + + it('returns the last attempted auth service if all of them have been tried', () => { + const auth = { + external: { isFetching: false, ok: false }, + login: { isFetching: false, ok: false }, + }; + expect(selectCurrentAuthServices({ ...state, auth }, { windowId: 'w' })[0].id).toEqual('login'); + expect(selectCurrentAuthServices({ ...state, auth }, { windowId: 'x' })[0].id).toEqual('external'); + expect(selectCurrentAuthServices({ ...state, auth }, { windowId: 'y' })[0]).toBeUndefined(); + }); + + 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: { + 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: { + a: { + json: manifestFixture001, + }, + }, + windows: { + w: { + manifestId: 'a', + visibleCanvases: [ + 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', + ], + }, + }, + }; + + it('returns external first', () => { + auth = {}; + expect(selectCurrentAuthServices({ ...orderedState, auth }, { windowId: 'w' })[0].id).toEqual('external'); + }); + + it('returns kiosk next', () => { + auth = { external: { isFetching: false, ok: false } }; + expect(selectCurrentAuthServices({ ...orderedState, auth }, { windowId: 'w' })[0].id).toEqual('kiosk'); + }); + + it('returns clickthrough next', () => { + auth = { + external: { isFetching: false, ok: false }, + kiosk: { isFetching: false, ok: false }, + }; + expect(selectCurrentAuthServices({ ...orderedState, auth }, { windowId: 'w' })[0].id).toEqual('clickthrough'); + }); + + it('returns logins last', () => { + auth = { + clickthrough: { isFetching: false, ok: false }, + external: { isFetching: false, ok: false }, + kiosk: { isFetching: false, ok: false }, + }; + expect(selectCurrentAuthServices({ ...orderedState, auth }, { windowId: 'w' })[0].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(selectCurrentAuthServices({ ...orderedState, auth }, { windowId: 'w' })[0].id).toEqual('login2'); + }); + }); +}); diff --git a/__tests__/src/selectors/canvases.test.js b/__tests__/src/selectors/canvases.test.js index b318e19ed95505d975c56fdbd525ae7a15d5378f..c6dcf8145bbf416670ddda930b6dcffe04cd8a93 100644 --- a/__tests__/src/selectors/canvases.test.js +++ b/__tests__/src/selectors/canvases.test.js @@ -10,7 +10,6 @@ import { getPreviousCanvasGrouping, getCanvas, getCanvasLabel, - selectCanvasAuthService, selectInfoResponse, getVisibleCanvasNonTiledResources, getVisibleCanvasIds, @@ -250,150 +249,6 @@ describe('getCanvasLabel', () => { }); }); -describe('selectCanvasAuthService', () => { - const resource = { - service: [ - { - '@id': 'external', - profile: 'http://iiif.io/api/auth/1/external', - }, - { - '@id': 'login', - profile: 'http://iiif.io/api/auth/1/login', - }, - ], - }; - const externalOnly = { - service: [ - { - '@id': 'external', - profile: 'http://iiif.io/api/auth/1/external', - }, - ], - }; - - const state = { - auth: {}, - config: { auth: settings.auth }, - infoResponses: { - 'https://iiif.bodleian.ox.ac.uk/iiif/image/9cca8fdd-4a61-4429-8ac1-f648764b4d6d': { - json: resource, - }, - 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44': { - json: externalOnly, - }, - }, - manifests: { - a: { - json: manifestFixture001, - }, - b: { - json: manifestFixture019, - }, - }, - }; - - it('returns undefined if there is no current canvas', () => { - expect(selectCanvasAuthService({ config: { auth: settings.auth }, manifests: {} }, { manifestId: 'a' })).toBeUndefined(); - }); - - it('returns the next auth service to try', () => { - expect(selectCanvasAuthService(state, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('external'); - }); - - it('returns the service if the next auth service is interactive', () => { - const auth = { external: { isFetching: false, ok: false } }; - expect(selectCanvasAuthService({ ...state, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('login'); - }); - - it('returns the last attempted auth service if all of them have been tried', () => { - const auth = { - external: { isFetching: false, ok: false }, - login: { isFetching: false, ok: false }, - }; - expect(selectCanvasAuthService({ ...state, auth }, { canvasId: 'https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json', manifestId: 'a' }).id).toEqual('login'); - 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('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: { - 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: { - a: { - json: manifestFixture001, - }, - }, - }; - - 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'); - }); - }); -}); - describe('selectInfoResponse', () => { it('returns in the info response for the first canvas resource', () => { const resource = { some: 'resource' }; diff --git a/src/containers/IIIFAuthentication.js b/src/containers/IIIFAuthentication.js index f1cd7f0db3f83006b87d0c2ab56533e1a5739984..eac81a4e861a2045a7309227f839af8bb8918e66 100644 --- a/src/containers/IIIFAuthentication.js +++ b/src/containers/IIIFAuthentication.js @@ -5,10 +5,9 @@ import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; import { - getCurrentCanvas, getAuth, - isInteractiveAuth, - selectCanvasAuthService, + getAuthProfiles, + selectCurrentAuthServices, getAccessTokens, } from '../state/selectors'; import { IIIFAuthentication } from '../components/IIIFAuthentication'; @@ -19,8 +18,10 @@ import { IIIFAuthentication } from '../components/IIIFAuthentication'; * @private */ const mapStateToProps = (state, { windowId }) => { - const canvasId = (getCurrentCanvas(state, { windowId }) || {}).id; - const service = selectCanvasAuthService(state, { canvasId, windowId }); + const services = selectCurrentAuthServices(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') @@ -31,12 +32,10 @@ const mapStateToProps = (state, { windowId }) => { || Utils.getService(service, 'http://iiif.io/api/auth/0/logout') ); - const authStatuses = getAuth(state) || {}; + const authStatuses = getAuth(state); const authStatus = service && authStatuses[service.id]; - const accessTokens = authStatus && accessTokenService && getAccessTokens(state); - const accessTokenStatus = accessTokens && Object.values(accessTokens).find( - e => e.id === accessTokenService.id, - ); + const accessTokens = getAccessTokens(state); + const accessTokenStatus = accessTokenService && accessTokens[accessTokenService.id]; let status; @@ -45,18 +44,20 @@ const mapStateToProps = (state, { windowId }) => { } else if (authStatus.ok) { status = 'ok'; } else if (authStatus.isFetching) { - if (authStatus.windowId === windowId) { - status = 'cookie'; - } + if (authStatus.windowId === windowId) status = 'cookie'; } else if (accessTokenStatus && accessTokenStatus.isFetching) { - if (authStatus.windowId === windowId) { - status = 'token'; - } + if (authStatus.windowId === windowId) status = 'token'; } else { status = 'failed'; } - const isInteractive = isInteractiveAuth(state, { canvasId, windowId }); + const authProfiles = getAuthProfiles(state); + + const profile = service && service.getProfile(); + + const isInteractive = authProfiles.some( + config => config.profile === profile && !(config.external || config.kiosk), + ); return { accessTokenServiceId: accessTokenService && accessTokenService.id, @@ -69,7 +70,7 @@ const mapStateToProps = (state, { windowId }) => { isInteractive, label: service && service.getLabel()[0].value, logoutServiceId: logoutService && logoutService.id, - profile: service && service.getProfile(), + profile, status, }; }; diff --git a/src/state/selectors/auth.js b/src/state/selectors/auth.js index 5e63c387ac41e8b46028e7bcdeab84331eeda438..0345021475d3f710a504ba520cc6c26ddae20851 100644 --- a/src/state/selectors/auth.js +++ b/src/state/selectors/auth.js @@ -1,9 +1,17 @@ import { createSelector } from 'reselect'; import { Utils } from 'manifesto.js/dist-esmodule/Utils'; +import flatten from 'lodash/flatten'; import MiradorCanvas from '../../lib/MiradorCanvas'; import { miradorSlice } from './utils'; import { getConfig } from './config'; -import { selectInfoResponse, getCanvas } from './canvases'; +import { getVisibleCanvases, selectInfoResponses } from './canvases'; + +export const getAuthProfiles = createSelector( + [ + getConfig, + ], + ({ auth: { serviceProfiles = [] } }) => serviceProfiles, +); /** */ export const getAccessTokens = state => miradorSlice(state).accessTokens || {}; @@ -11,72 +19,65 @@ export const getAccessTokens = state => miradorSlice(state).accessTokens || {}; /** */ export const getAuth = state => miradorSlice(state).auth || {}; -export const selectCanvasAuthService = createSelector( +export const selectCurrentAuthServices = createSelector( [ - selectInfoResponse, - getCanvas, - getConfig, + getVisibleCanvases, + selectInfoResponses, + getAuthProfiles, getAuth, + (state, { iiifResources }) => iiifResources, ], - (infoResponse, canvas, { auth: { serviceProfiles = [] } }, auth) => { - let iiifResource; - iiifResource = infoResponse && infoResponse.json && { ...infoResponse.json, options: {} }; + (canvases, infoResponses = {}, serviceProfiles, auth, iiifResources) => { + let currentAuthResources = iiifResources; - if (!iiifResource && canvas) { - const miradorCanvas = new MiradorCanvas(canvas); - const [image] = miradorCanvas.iiifImageResources; + if (!currentAuthResources && canvases) { + currentAuthResources = flatten(canvases.map(c => { + const miradorCanvas = new MiradorCanvas(c); + const images = miradorCanvas.iiifImageResources; - iiifResource = image && image.getServices()[0]; - } + return images.map(i => { + const iiifImageService = i.getServices()[0]; - if (!iiifResource) return undefined; + const infoResponse = infoResponses[iiifImageService.id]; + if (infoResponse && infoResponse.json) { + return { ...infoResponse.json, options: {} }; + } + + return iiifImageService; + }); + })); + } - const orderedAuthServiceProfiles = serviceProfiles.map(p => p.profile); + if (!currentAuthResources) return []; + if (currentAuthResources.length === 0) return []; - let lastAttemptedService; + const currentAuthServices = currentAuthResources.map(resource => { + let lastAttemptedService; + const services = Utils.getServices(resource); - for (const profile of orderedAuthServiceProfiles) { - const services = getServices(iiifResource, profile); - for (const service of services) { - if (!auth[service.id]) return service; + for (const authProfile of serviceProfiles) { + const profiledAuthServices = services.filter( + p => authProfile.profile === p.getProfile(), + ); - lastAttemptedService = service; + for (const service of profiledAuthServices) { + lastAttemptedService = service; - if (auth[service.id].isFetching || auth[service.id].ok) return service; + if (!auth[service.id] || auth[service.id].isFetching || auth[service.id].ok) { + return service; + } + } } - } - return lastAttemptedService; - }, -); + return lastAttemptedService; + }); -/** */ -export function selectAuthStatus({ auth }, service) { - if (!service) return null; - if (!auth[service.id]) return null; - if (auth[service.id].isFetching) return 'fetching'; - if (auth[service.id].ok) return 'ok'; - return 'failed'; -} - -/** Get all the services that match a profile */ -function getServices(resource, profile) { - const services = Utils.getServices(resource); - - return services.filter(service => service.getProfile() === profile); -} - -/** check if the current auth service is "interactive" */ -export const isInteractiveAuth = createSelector( - [ - selectCanvasAuthService, - getConfig, - ], - (service, { auth: { serviceProfiles = [] } }) => { - const profile = service && service.getProfile(); + return Object.values(currentAuthServices.reduce((h, service) => { + if (service && !h[service.id]) { + h[service.id] = service; // eslint-disable-line no-param-reassign + } - return serviceProfiles.some( - config => config.profile === profile && !(config.external || config.kiosk), - ); + return h; + }, {})); }, );