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