Skip to content

Commit

Permalink
Updates for testing on test env
Browse files Browse the repository at this point in the history
  • Loading branch information
ksclarke committed Feb 13, 2025
1 parent a9264d6 commit 8b53da4
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 62 deletions.
9 changes: 0 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ jobs:
java: [ 17 ]

steps:
- name: Set up build cache
uses: actions/cache@0781355a23dac32fd3bac414512f4b903437991a # v2
if: ${{ env.MAVEN_CACHE_KEY }}
with:
path: |
~/.m2
lib
key: uclalibrary-cache-${{ env.MAVEN_CACHE_KEY }}-${{ hashFiles('**/pom.xml') }}
restore-keys: uclalibrary-cache-${{ env.MAVEN_CACHE_KEY }}-
- name: Check out code
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2
with:
Expand Down
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@
<docker.maven.plugin.version>0.45.1</docker.maven.plugin.version>

<!-- Docker images versions used in testing -->
<hauth.container.version>0.0.7</hauth.container.version>
<hauth.container.version>1.0.7</hauth.container.version>
<psql.container.version>12.7-alpine</psql.container.version>
<redis.container.version>6.2.5-alpine</redis.container.version>
<cantaloupe.container.version>5.0.6</cantaloupe.container.version>
<!--<cantaloupe.container.version>5.0.6-6</cantaloupe.container.version>-->
<cantaloupe.container.version>5.0.6-4</cantaloupe.container.version>

<!-- Build-time options -->
<update.sql>false</update.sql>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import info.freelibrary.util.HTTP;
import info.freelibrary.util.Logger;
import info.freelibrary.util.LoggerFactory;

import info.freelibrary.iiif.presentation.v3.services.AuthCookieService1;
import info.freelibrary.iiif.presentation.v3.services.AuthTokenService1;
import info.freelibrary.iiif.presentation.v3.services.ExternalCookieService1;
Expand All @@ -33,6 +34,7 @@
import edu.ucla.library.iiif.auth.delegate.hauth.HauthItem;
import edu.ucla.library.iiif.auth.delegate.hauth.HauthSinaiToken;
import edu.ucla.library.iiif.auth.delegate.hauth.HauthToken;

import edu.illinois.library.cantaloupe.delegate.JavaContext;
import edu.illinois.library.cantaloupe.delegate.JavaDelegate;

Expand All @@ -52,9 +54,9 @@ public class HauthDelegate extends CantaloupeDelegate implements JavaDelegate {
private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<>() {};

/**
* The default thumbnail dimensions.
* The way we identify if a thumbnail is being requested.
*/
private static final String THUMBNAIL_DIMS = "/!200,200/";
private static final String THUMBNAIL = "/full/!200,200/0/";

/**
* The name of the Cookie HTTP request header.
Expand Down Expand Up @@ -135,8 +137,9 @@ public Object preAuthorize() {
final JavaContext context = getContext();
final String id = context.getIdentifier();

// We let all thumbnail requests through regardless of authorization
if (context.getLocalURI().contains(THUMBNAIL_DIMS)) {
// Allow any kind of thumbnail request, no questions asked
if (context.getLocalURI().contains(THUMBNAIL)) {
LOGGER.debug(MessageCodes.CAD_030, context.getLocalURI());
return true;
}

Expand Down Expand Up @@ -202,18 +205,21 @@ private Object getTieredInfo() {
return true;
}

// Degraded image request for the size we allow (probably via an earlier HTTP 302 redirect)
// Degraded image request for the size we allow (probably via an earlier HTTP
// 302 redirect)
if (Arrays.equals(configuredScaleConstraint, scaleConstraint)) {
return myInfoJsonShouldContainAuth = true;
}

// Degraded image request for a size that doesn't match what we've configured and isn't 1:1
// Degraded image request for a size that doesn't match what we've configured
// and isn't 1:1
if (scaleConstraint[0] != scaleConstraint[1]) {
LOGGER.debug(MessageCodes.CAD_015, scaleConstraint[0], scaleConstraint[1]);
return false; // returns 403
}

// Full image request, but non-campus IP (the long types make a difference here, apparently)
// Full image request, but non-campus IP (the long types make a difference here,
// apparently)
LOGGER.debug(MessageCodes.CAD_016);
return Map.of(STATUS_CODE, Long.valueOf(HTTP.FOUND), //
SCALE_NUMERATOR, (long) configuredScaleConstraint[0], //
Expand All @@ -232,7 +238,8 @@ private Object getTieredImage() {

LOGGER.debug(MessageCodes.CAD_017);

// Degraded image request for the size we allow (probably via an earlier HTTP 302 redirect)
// Degraded image request for the size we allow (probably via an earlier HTTP
// 302 redirect)
if (Arrays.equals(configuredScaleConstraint, scaleConstraint)) {
LOGGER.debug(MessageCodes.CAD_027);
return true;
Expand All @@ -250,7 +257,8 @@ private Object getTieredImage() {
return true;
}

// Full image request, but non-campus IP (the long types make a difference here, apparently)
// Full image request, but non-campus IP (the long types make a difference here,
// apparently)
LOGGER.debug(MessageCodes.CAD_019);
return Map.of(STATUS_CODE, Long.valueOf(HTTP.FOUND), //
SCALE_NUMERATOR, (long) configuredScaleConstraint[0], //
Expand Down Expand Up @@ -324,11 +332,13 @@ private Map<String, Object> getAuthServices() {
break;
case OPEN:
default:
// The OPEN and default branches should not be reachable, but are included here just in case
// The OPEN and default branches should not be reachable, but are included here
// just in case
return Collections.emptyMap();
}

// Workaround for Mirador bug that requires label be present (Cf. https://bitly.com/3NllMLq+)
// Workaround for Mirador bug that requires label be present (Cf.
// https://bitly.com/3NllMLq+)
serviceMap = JSON.convertValue(cookieService, MAP_TYPE_REFERENCE);
serviceMap.putIfAbsent(JsonKeys.LABEL, label);

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/auth-delegate_messages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
<entry key="CAD-027">Access allowed: Degraded image request for the size we allow</entry>
<entry key="CAD-028">Access denied: Degraded image request for a size we don't allow: {}:{}</entry>
<entry key="CAD-029">Request header "{}" not found</entry>
<entry key="CAD-030">Letting a thumbnail request through: {}</entry>

</properties>
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
*/
public class HauthDelegateIT {

/**
* A sample thumbnail IIIF request.
*/
private static final String THUMBNAIL = "/full/!200,200/0/default.tif";

/**
* The template for image URLs. The slots are:
* <ul>
Expand All @@ -54,7 +59,7 @@ public class HauthDelegateIT {
* </code>
* <p>
* This would be the value of the "accessToken" key shown
* <a href="https://iiif.io/api/auth/1.0/#the-json-access-token-response">here</a>.
* <a href= "https://iiif.io/api/auth/1.0/#the-json-access-token-response">here</a>.
*/
private static final String ACCESS_TOKEN =
"eyJ2ZXJzaW9uIjogIjAuMC4wLVNOQVBTSE9UIiwgImNhbXB1c05ldHdvcmsiOiB0cnVlfQo=";
Expand All @@ -66,39 +71,11 @@ public class HauthDelegateIT {
* </code>
* <p>
* This would be the value of the "accessToken" key shown
* <a href="https://iiif.io/api/auth/1.0/#the-json-access-token-response">here</a>.
* <a href= "https://iiif.io/api/auth/1.0/#the-json-access-token-response">here</a>.
*/
private static final String SINAI_ACCESS_TOKEN =
"eyJ2ZXJzaW9uIjogIjAuMC4wLVNOQVBTSE9UIiwgInNpbmFpQWZmaWxpYXRlIjogdHJ1ZX0K";

/**
* A test initialization vector used to encrypt {@link #TEST_SINAI_AUTHENTICATED_3DAY}. This is just the value
* "0123456789ABCDEF" (see Ruby script below) encoded in hexadecimal.
*/
private static final String TEST_INITIALIZATION_VECTOR = "30313233343536373839414243444546";

/**
* A test cookie generated using the following Ruby code, mocking the relevant part of the Sinai application.
* <p>
*
* <pre>
* #!/usr/bin/env ruby
*
* require "openssl"
*
* cipher = OpenSSL::Cipher::AES256.new :CBC
* cipher.encrypt
* cipher.key = "ThisPasswordIsReallyHardToGuess!"
* cipher.iv = "0123456789ABCDEF"
* puts (cipher.update("Authenticated #{Time.at(0).utc}") + cipher.final).unpack("H*")[0].upcase
* </pre>
*
* @see <a href= "https://github.com/UCLALibrary/sinaimanuscripts/blob/44cbbd9bf508c32b742f1617205a679edf77603e/app/
* controllers/application_controller.rb#L98-L103">How the Sinai application encodes cookies</a>
*/
private static final String TEST_SINAI_AUTHENTICATED_3DAY =
"5AFF80488740353F8A11B99C7A493D871807521908500772B92E4F8FC919E305A607ADB714B22EF08D2C22FC08C8A6EC";

/**
* The id of the non-restricted image.
*/
Expand Down Expand Up @@ -183,12 +160,38 @@ public class HauthDelegateIT {

/**
* Tests that thumbnails of access controlled items are still displayed.
*
* @throws InterruptedException If the test is interrupted
* @throws IOException If there is trouble reading and writing test resources
*/
@Test
public final void testAccessControlledThumbnails() throws InterruptedException, IOException {
final String imageURL =
StringUtils.format(IMAGE_URL_TEMPLATE, System.getenv().get(TestConfig.IIIF_URL_PROPERTY), 2,
ALL_OR_NOTHING_ACCESS_IMAGE + "/full/!200,200/0/default.tif");
public final void testAllOrNothingThumbnails() throws InterruptedException, IOException {
final String imageURL = StringUtils.format(IMAGE_URL_TEMPLATE,
System.getenv().get(TestConfig.IIIF_URL_PROPERTY), 2, ALL_OR_NOTHING_ACCESS_IMAGE + THUMBNAIL);
final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(imageURL));
final HttpResponse<byte[]> response = HTTP_CLIENT.send(requestBuilder.build(), BodyHandlers.ofByteArray());
final ByteArrayInputStream byteArrayInputStream;
final BufferedImage image;

assertEquals(200, response.statusCode());

byteArrayInputStream = new ByteArrayInputStream(response.body());
image = ImageIO.read(byteArrayInputStream);

assertEquals(200, image.getHeight());
assertEquals(200, image.getWidth());
}

/**
* Tests that thumbnails of access controlled items are still displayed.
*
* @throws InterruptedException If the test is interrupted
* @throws IOException If there is trouble reading and writing test resources
*/
@Test
public final void testTieredAccessControlledThumbnails() throws InterruptedException, IOException {
final String imageURL = StringUtils.format(IMAGE_URL_TEMPLATE,
System.getenv().get(TestConfig.IIIF_URL_PROPERTY), 2, TIERED_ACCESS_IMAGE_DEGRADED_VALID + THUMBNAIL);
final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(imageURL));
final HttpResponse<byte[]> response = HTTP_CLIENT.send(requestBuilder.build(), BodyHandlers.ofByteArray());
final ByteArrayInputStream byteArrayInputStream;
Expand Down Expand Up @@ -350,7 +353,8 @@ private static String getImageURL(final String aBaseURL, final int aImageApiVers
final String imageApiPathTemplate;
final String imageApiPath;

// Use TIFFs so that we can easily compare the response payload with the source image
// Use TIFFs so that we can easily compare the response payload with the source
// image
switch (aImageApiVersion) {
case 2:
imageApiPathTemplate = "{}/full/full/0/default.tif";
Expand Down Expand Up @@ -481,6 +485,8 @@ public final void testErrorResponseTieredDisallowedScale() throws IOException, I
final HttpResponse<String> response =
sendImageInfoRequest(TIERED_ACCESS_IMAGE_DEGRADED_UNAVAILABLE, null, 2);

System.out.println(response.headers().toString());

assertEquals(HTTP.FORBIDDEN, response.statusCode());
assertFalse(TestUtils.responseHasContentType(response, MediaType.APPLICATION_JSON,
MediaType.APPLICATION_LD_PLUS_JSON));
Expand All @@ -506,6 +512,9 @@ public final void testNoAccessResponseAllOrNothingUnauthorized() throws IOExcept
getExpectedImageInfo(ALL_OR_NOTHING_ACCESS_IMAGE, NO_ACCESS_RESPONSE_TEMPLATE_V2, 2);

assertEquals(HTTP.UNAUTHORIZED, response.statusCode());

System.out.println(response.headers().toString());

assertTrue(TestUtils.responseHasContentType(response, MediaType.APPLICATION_JSON,
MediaType.APPLICATION_LD_PLUS_JSON));
TestUtils.assertEquals(expectedResponse, response.body());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.TreeMap;
import java.util.stream.Stream;
Expand Down Expand Up @@ -112,11 +113,9 @@ public static String[] getMockSinaiCookieValues() throws IOException {
* @return Whether or not at least one of the content types is included in the response's Content-Type header
*/
public static boolean responseHasContentType(final HttpResponse<?> aResponse, final MediaType... aMediaTypes) {
final String contentTypeHeader = aResponse.headers().firstValue(HttpHeaders.CONTENT_TYPE).get();

return Stream.of(aMediaTypes).map(String::valueOf).anyMatch(value -> {
return contentTypeHeader.contains(value);
});
final Optional<String> contentType = aResponse.headers().firstValue(HttpHeaders.CONTENT_TYPE);
return contentType.isPresent() &&
Stream.of(aMediaTypes).map(String::valueOf).anyMatch(value -> contentType.get().contains(value));
}

/**
Expand Down

0 comments on commit 8b53da4

Please sign in to comment.