From c3a6e9a99e155bc3f379924035a094551b9c6d71 Mon Sep 17 00:00:00 2001 From: Alex Dolski Date: Fri, 25 Mar 2022 14:16:15 -0500 Subject: [PATCH] Allow sending info.json XHR with Authorization header (#574) Web browser clients implementing IIIF Authentication API 1.0 may send info.json requests with an Authorization header via XMLHttpRequest (XHR). Such requests are "pre-flighted", and the pre-flight response must explicitly state that the Authorization header is allowed in order for the browser to proceed with the request. --- .../cantaloupe/resource/AbstractResource.java | 5 ++++- .../resource/iiif/v1/InformationResource.java | 19 +++++++++++++++++++ .../resource/iiif/v2/InformationResource.java | 18 ++++++++++++++++++ .../resource/iiif/v3/InformationResource.java | 18 ++++++++++++++++++ .../iiif/v1/InformationResourceTest.java | 5 +++++ .../iiif/v2/InformationResourceTest.java | 5 +++++ .../iiif/v3/InformationResourceTest.java | 5 +++++ 7 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java index 7b2c04e8d..91dee3a78 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/AbstractResource.java @@ -198,7 +198,10 @@ public void doHEAD() throws Exception { doGET(); } - final void doOPTIONS() { + /** + * May be overridden by implementations that support {@literal OPTIONS}. + */ + protected void doOPTIONS() { Method[] methods = getSupportedMethods(); if (methods.length > 0) { response.setStatus(Status.NO_CONTENT.getCode()); diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResource.java index 4a4c776de..a3fede3b9 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResource.java @@ -1,10 +1,12 @@ package edu.illinois.library.cantaloupe.resource.iiif.v1; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import edu.illinois.library.cantaloupe.http.Method; import edu.illinois.library.cantaloupe.http.Status; @@ -18,6 +20,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jakarta.servlet.http.HttpServletResponse; + /** * Handles IIIF Image API 1.x information requests. * @@ -42,6 +46,21 @@ public Method[] getSupportedMethods() { return SUPPORTED_METHODS; } + @Override + protected final void doOPTIONS() { + HttpServletResponse response = getResponse(); + Method[] methods = getSupportedMethods(); + if (methods.length > 0) { + response.setStatus(Status.NO_CONTENT.getCode()); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + response.setHeader("Allow", Arrays.stream(methods) + .map(Method::toString) + .collect(Collectors.joining(","))); + } else { + response.setStatus(Status.METHOD_NOT_ALLOWED.getCode()); + } + } + /** * Writes a JSON-serialized {@link Information} instance to the response. */ diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResource.java index 19c695694..9c72978a8 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResource.java @@ -1,10 +1,12 @@ package edu.illinois.library.cantaloupe.resource.iiif.v2; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import edu.illinois.library.cantaloupe.http.Method; import edu.illinois.library.cantaloupe.http.Status; @@ -20,6 +22,7 @@ import org.slf4j.LoggerFactory; import javax.script.ScriptException; +import jakarta.servlet.http.HttpServletResponse; /** * Handles information requests. @@ -45,6 +48,21 @@ public Method[] getSupportedMethods() { return SUPPORTED_METHODS; } + @Override + protected final void doOPTIONS() { + HttpServletResponse response = getResponse(); + Method[] methods = getSupportedMethods(); + if (methods.length > 0) { + response.setStatus(Status.NO_CONTENT.getCode()); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + response.setHeader("Allow", Arrays.stream(methods) + .map(Method::toString) + .collect(Collectors.joining(","))); + } else { + response.setStatus(Status.METHOD_NOT_ALLOWED.getCode()); + } + } + /** * Writes a JSON-serialized {@link Information} instance to the response. */ diff --git a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResource.java b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResource.java index 125130cf9..f5ff30fbc 100644 --- a/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResource.java +++ b/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResource.java @@ -1,10 +1,12 @@ package edu.illinois.library.cantaloupe.resource.iiif.v3; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import edu.illinois.library.cantaloupe.http.Method; import edu.illinois.library.cantaloupe.http.Status; @@ -20,6 +22,7 @@ import org.slf4j.LoggerFactory; import javax.script.ScriptException; +import jakarta.servlet.http.HttpServletResponse; /** * Handles IIIF Image API 3.x information requests. @@ -45,6 +48,21 @@ public Method[] getSupportedMethods() { return SUPPORTED_METHODS; } + @Override + protected final void doOPTIONS() { + HttpServletResponse response = getResponse(); + Method[] methods = getSupportedMethods(); + if (methods.length > 0) { + response.setStatus(Status.NO_CONTENT.getCode()); + response.setHeader("Access-Control-Allow-Headers", "Authorization"); + response.setHeader("Allow", Arrays.stream(methods) + .map(Method::toString) + .collect(Collectors.joining(","))); + } else { + response.setStatus(Status.METHOD_NOT_ALLOWED.getCode()); + } + } + /** * Writes a JSON-serialized {@link Information} instance to the response. */ diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResourceTest.java index af9dfa9db..251d2526d 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v1/InformationResourceTest.java @@ -603,6 +603,11 @@ void testOPTIONSWhenEnabled() throws Exception { assertEquals(2, methods.size()); assertTrue(methods.contains("GET")); assertTrue(methods.contains("OPTIONS")); + + List allowedHeaders = + List.of(StringUtils.split(headers.getFirstValue("Access-Control-Allow-Headers"), ", ")); + assertEquals(1, allowedHeaders.size()); + assertTrue(allowedHeaders.contains("Authorization")); } @Test diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResourceTest.java index 021d93927..a8b6e2b0a 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v2/InformationResourceTest.java @@ -623,6 +623,11 @@ void testOPTIONSWhenEnabled() throws Exception { assertEquals(2, methods.size()); assertTrue(methods.contains("GET")); assertTrue(methods.contains("OPTIONS")); + + List allowedHeaders = + List.of(StringUtils.split(headers.getFirstValue("Access-Control-Allow-Headers"), ", ")); + assertEquals(1, allowedHeaders.size()); + assertTrue(allowedHeaders.contains("Authorization")); } @Test diff --git a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResourceTest.java b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResourceTest.java index 38e55cec7..687fc9263 100644 --- a/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResourceTest.java +++ b/src/test/java/edu/illinois/library/cantaloupe/resource/iiif/v3/InformationResourceTest.java @@ -600,6 +600,11 @@ void testOPTIONSWhenEnabled() throws Exception { assertEquals(2, methods.size()); assertTrue(methods.contains("GET")); assertTrue(methods.contains("OPTIONS")); + + List allowedHeaders = + List.of(StringUtils.split(headers.getFirstValue("Access-Control-Allow-Headers"), ", ")); + assertEquals(1, allowedHeaders.size()); + assertTrue(allowedHeaders.contains("Authorization")); } @Test