diff --git a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapper.java b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapper.java index 5c8100b..15a030e 100644 --- a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapper.java +++ b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapper.java @@ -3,7 +3,6 @@ import static com.oviva.ehealthid.relyingparty.svc.ValidationException.*; import static com.oviva.ehealthid.relyingparty.util.LocaleUtils.getNegotiatedLocale; -import com.fasterxml.jackson.annotation.JsonProperty; import com.oviva.ehealthid.auth.AuthException; import com.oviva.ehealthid.fedclient.FederationException; import com.oviva.ehealthid.relyingparty.svc.AuthenticationException; @@ -21,11 +20,8 @@ import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.ext.ExceptionMapper; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.jboss.resteasy.util.MediaTypeHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.spi.LoggingEventBuilder; @@ -135,22 +131,4 @@ private LoggingEventBuilder addRequestContext(LoggingEventBuilder builder) { return builder.addKeyValue("httpRequest", map); } - - interface MediaTypeNegotiator { - MediaType bestMatch(List desiredMediaType, List supportedMediaTypes); - } - - private static class ResteasyMediaTypeNegotiator implements MediaTypeNegotiator { - - @Override - public MediaType bestMatch( - List desiredMediaType, List supportedMediaTypes) { - - // note: resteasy needs mutable lists - return MediaTypeHelper.getBestMatch( - new ArrayList<>(desiredMediaType), new ArrayList<>(supportedMediaTypes)); - } - } - - public record Problem(@JsonProperty("type") String type, @JsonProperty("title") String title) {} } diff --git a/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapperTest.java b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapperTest.java index 87843dd..cc1d358 100644 --- a/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapperTest.java +++ b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/ws/ThrowableExceptionMapperTest.java @@ -7,16 +7,16 @@ import static org.mockito.Mockito.*; import com.github.mustachejava.util.HtmlEscaper; +import com.oviva.ehealthid.auth.AuthException; +import com.oviva.ehealthid.fedclient.FederationException; import com.oviva.ehealthid.relyingparty.svc.AuthenticationException; import com.oviva.ehealthid.relyingparty.svc.ValidationException; -import com.oviva.ehealthid.relyingparty.ws.ThrowableExceptionMapper.Problem; import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.ServerErrorException; import jakarta.ws.rs.core.*; import java.io.StringWriter; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -145,7 +145,6 @@ void toResponse_authentication() { void toResponse_withBody() { when(uriInfo.getRequestUri()).thenReturn(REQUEST_URI); - when(headers.getAcceptableMediaTypes()).thenReturn(List.of(MediaType.WILDCARD_TYPE)); mockHeaders("de-DE"); // when @@ -157,27 +156,6 @@ void toResponse_withBody() { assertNotNull(res.getEntity()); } - @Test - void toResponse_withJson() { - - when(headers.getAcceptableMediaTypes()) - .thenReturn( - List.of( - MediaType.APPLICATION_JSON_TYPE, - MediaType.TEXT_HTML_TYPE, - MediaType.WILDCARD_TYPE)); - - var msg = "Ooops! An error :/"; - - // when - var res = mapper.toResponse(new ValidationException(new Message(msg))); - - // then - assertEquals(400, res.getStatus()); - assertEquals(MediaType.APPLICATION_JSON_TYPE, res.getMediaType()); - assertEquals(new Problem("/server_error", msg), res.getEntity()); - } - @Test void toResponse_withBody_Unauthorized() { // when @@ -201,9 +179,8 @@ void toResponse_withBody_seeOthers() { } @Test - void toResponse_withBody_withValidationExc() { + void toResponse_withBody_withValidationException() { - when(headers.getAcceptableMediaTypes()).thenReturn(List.of(MediaType.WILDCARD_TYPE)); doReturn("de-DE").when(headers).getHeaderString("Accept-Language"); // when @@ -211,7 +188,42 @@ void toResponse_withBody_withValidationExc() { // then assertEquals(400, res.getStatus()); - System.out.println(res.getEntity()); + assertEquals(MediaType.TEXT_HTML_TYPE, res.getMediaType()); + assertNotNull(res.getEntity()); + } + + @Test + void toResponse_withBody_withFederationException() { + + doReturn("de-DE").when(headers).getHeaderString("Accept-Language"); + doReturn(URI.create("https://example.com")).when(uriInfo).getRequestUri(); + doReturn(null).when(headers).getHeaderString("user-agent"); + + // when + var res = + mapper.toResponse( + new FederationException("Nope!", FederationException.Reason.BAD_FEDERATION_MASTER)); + + // then + assertEquals(500, res.getStatus()); + assertEquals(MediaType.TEXT_HTML_TYPE, res.getMediaType()); + assertNotNull(res.getEntity()); + } + + @Test + void toResponse_withBody_withAuthenticationException() { + + doReturn("de-DE").when(headers).getHeaderString("Accept-Language"); + doReturn(URI.create("https://example.com")).when(uriInfo).getRequestUri(); + doReturn(null).when(headers).getHeaderString("user-agent"); + + // when + var res = + mapper.toResponse( + new AuthException("Nope!", AuthException.Reason.INVALID_ID_TOKEN)); + + // then + assertEquals(500, res.getStatus()); assertEquals(MediaType.TEXT_HTML_TYPE, res.getMediaType()); assertNotNull(res.getEntity()); } @@ -221,7 +233,6 @@ void toResponse_withBody_withValidationExc() { void toResponse_withBody_withValidationExceptionAndDynamicContent( String language, String messageKey, String message) { - when(headers.getAcceptableMediaTypes()).thenReturn(List.of(MediaType.WILDCARD_TYPE)); doReturn(language).when(headers).getHeaderString("Accept-Language"); // when diff --git a/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthException.java b/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthException.java index 59a8ac2..844cdb5 100644 --- a/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthException.java +++ b/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthException.java @@ -21,9 +21,9 @@ public Reason reason() { public enum Reason { UNKNOWN, INVALID_PAR_URI, - MISSING_PAR_URI, + MISSING_PAR_ENDPOINT, FAILED_PAR_REQUEST, - MISSING_AUTHORIZATION_URL, + MISSING_AUTHORIZATION_ENDPOINT, MISSING_OPENID_CONFIGURATION_IN_ENTITY_STATEMENT, INVALID_ID_TOKEN } diff --git a/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthExceptions.java b/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthExceptions.java index 3acd954..1d6c2a7 100644 --- a/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthExceptions.java +++ b/ehealthid/src/main/java/com/oviva/ehealthid/auth/AuthExceptions.java @@ -4,20 +4,22 @@ public class AuthExceptions { private AuthExceptions() {} public static AuthException invalidParRequestUri(String uri) { + // the field is called `request_uri`, hence the uri instead of url naming return new AuthException( "invalid par request_uri '%s'".formatted(uri), AuthException.Reason.INVALID_PAR_URI); } - public static AuthException missingAuthorizationUrl(String sub) { + public static AuthException missingAuthorizationEndpoint(String sub) { return new AuthException( - "entity statement of '%s' has no authorization url configuration".formatted(sub), - AuthException.Reason.MISSING_AUTHORIZATION_URL); + "entity statement of '%s' has no authorization endpoint configuration".formatted(sub), + AuthException.Reason.MISSING_AUTHORIZATION_ENDPOINT); } - public static AuthException missingParUrl(String sub) { + public static AuthException missingParEndpoint(String sub) { return new AuthException( - "entity statement of '%s' has no pushed authorization request configuration".formatted(sub), - AuthException.Reason.MISSING_PAR_URI); + "entity statement of '%s' has no pushed authorization request endpoint configuration" + .formatted(sub), + AuthException.Reason.MISSING_PAR_ENDPOINT); } public static AuthException failedParRequest(String issuer, Exception cause) { diff --git a/ehealthid/src/main/java/com/oviva/ehealthid/auth/internal/steps/SelectSectoralIdpStepImpl.java b/ehealthid/src/main/java/com/oviva/ehealthid/auth/internal/steps/SelectSectoralIdpStepImpl.java index 8abf6e3..0efea8b 100644 --- a/ehealthid/src/main/java/com/oviva/ehealthid/auth/internal/steps/SelectSectoralIdpStepImpl.java +++ b/ehealthid/src/main/java/com/oviva/ehealthid/auth/internal/steps/SelectSectoralIdpStepImpl.java @@ -111,7 +111,7 @@ private URI buildAuthorizationUrl(String parRequestUri, EntityStatement trustedE var authzEndpoint = openidConfig.authorizationEndpoint(); if (authzEndpoint == null || authzEndpoint.isBlank()) { - throw AuthExceptions.missingAuthorizationUrl(trustedEntityStatement.sub()); + throw AuthExceptions.missingAuthorizationEndpoint(trustedEntityStatement.sub()); } return UriBuilder.fromUri(authzEndpoint) @@ -126,7 +126,7 @@ private ParResponse doPushedAuthorizationRequest( var openidConfig = getIdpOpenIdProvider(trustedEntityStatement); var parEndpoint = openidConfig.pushedAuthorizationRequestEndpoint(); if (parEndpoint == null || parEndpoint.isBlank()) { - throw AuthExceptions.missingParUrl(trustedEntityStatement.sub()); + throw AuthExceptions.missingParEndpoint(trustedEntityStatement.sub()); } return openIdClient.requestPushedUri(URI.create(parEndpoint), builder); diff --git a/ehealthid/src/test/java/com/oviva/ehealthid/auth/AuthExceptionsTest.java b/ehealthid/src/test/java/com/oviva/ehealthid/auth/AuthExceptionsTest.java new file mode 100644 index 0000000..5711551 --- /dev/null +++ b/ehealthid/src/test/java/com/oviva/ehealthid/auth/AuthExceptionsTest.java @@ -0,0 +1,57 @@ +package com.oviva.ehealthid.auth; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AuthExceptionsTest { + + @Test + void invalidParRequestUri() { + var got = AuthExceptions.invalidParRequestUri("https://example.com"); + assertEquals(AuthException.Reason.INVALID_PAR_URI, got.reason()); + } + + @Test + void missingAuthorizationEndpoint() { + var got = AuthExceptions.missingAuthorizationEndpoint("https://example.com"); + assertEquals(AuthException.Reason.MISSING_AUTHORIZATION_ENDPOINT, got.reason()); + } + + @Test + void missingParEndpoint() { + var got = AuthExceptions.missingParEndpoint("https://example.com"); + assertEquals(AuthException.Reason.MISSING_PAR_ENDPOINT, got.reason()); + } + + @Test + void failedParRequest() { + var cause = new IllegalArgumentException(); + var got = AuthExceptions.failedParRequest("https://fedmaster.example.com", cause); + + assertEquals(AuthException.Reason.FAILED_PAR_REQUEST, got.reason()); + assertEquals(cause, got.getCause()); + } + + @Test + void missingOpenIdConfigurationInEntityStatement() { + var got = AuthExceptions.missingOpenIdConfigurationInEntityStatement("https://example.com"); + + assertEquals( + AuthException.Reason.MISSING_OPENID_CONFIGURATION_IN_ENTITY_STATEMENT, got.reason()); + } + + @Test + void badIdTokenSignature() { + var got = AuthExceptions.badIdTokenSignature("https://example.com"); + assertEquals(AuthException.Reason.INVALID_ID_TOKEN, got.reason()); + } + + @Test + void badIdToken() { + var cause = new IllegalArgumentException(); + var got = AuthExceptions.badIdToken("https://example.com", cause); + assertEquals(AuthException.Reason.INVALID_ID_TOKEN, got.reason()); + assertEquals(cause, got.getCause()); + } +}