diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java index 38de96242..8176f1880 100644 --- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/VertxMicroserviceEngine.java @@ -1,6 +1,8 @@ package net.trajano.ms.vertx; import java.io.File; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Deque; import java.util.LinkedList; @@ -48,6 +50,10 @@ public class VertxMicroserviceEngine implements @Autowired private HttpServerOptions httpServerOptions; + private String theHostname; + + private int thePort = -1; + private Vertx vertx; @Autowired @@ -73,6 +79,18 @@ public Object[] bootstrap() { }; } + @Override + public String hostname() { + + return theHostname; + } + + @Override + public int port() { + + return thePort; + } + @PostConstruct public void start() { @@ -113,8 +131,14 @@ public void start() { SpringApplication.exit(applicationContext, () -> -1); } else { LOG.info("Listening on port {}", http.actualPort()); + thePort = http.actualPort(); } }); + try { + theHostname = InetAddress.getLocalHost().getHostName(); + } catch (final UnknownHostException e) { + throw new ExceptionInInitializerError(e); + } } @PreDestroy diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java index ba1ec1a7f..798ad87ba 100644 --- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/CachedDataProvider.java @@ -69,6 +69,8 @@ public JwtConsumer buildConsumer(final HttpsJwks jwks, if (jwks != null) { builder .setVerificationKeyResolver(new HttpsJwksVerificationKeyResolver(jwks)); + } else { + builder.setSkipSignatureVerification(); } if (audience != null) { builder diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java index 8117e258b..8567d0068 100644 --- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/DefaultAssertionRequiredPredicate.java @@ -27,6 +27,44 @@ public class DefaultAssertionRequiredPredicate implements private static final Logger LOG = LoggerFactory.getLogger(DefaultAssertionRequiredPredicate.class); + /** + *

+ * The key logic for the test is as follows: + *

+ *

+ * Let: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
a =resourceMethodHasRolesAllowed
b =resourceClassHasRolesAllowed
c =resourceMethodHasPermitAll
d =resourceClassHasPermitAll
+ *

+ * The rules that need to be applied translate to: + *

+ * + *
+     * = a || ( b && !c ) || ( !a && !b && !c && !d )
+     * = (a || b || !d) && ( a || !c )
+     * 
+ * + * @param resourceInfo + * resource info + * @return true if the resource is protected + */ @Override public boolean test(final ResourceInfo resourceInfo) { @@ -49,13 +87,6 @@ public boolean test(final ResourceInfo resourceInfo) { } else if (resourceClassHasRolesAllowed && resourceClassHasPermitAll) { throw new IllegalArgumentException("The resource class " + resourceClass + " may not have both @RolesAllowed and @PermitAll annotations."); } else { - // resourceMethodHasRolesAllowed OR - // resourceClassHasRolesAllowed && !resourceMethodHasPermitAll OR - // !resourceMethodHasRolesAllowed && !resourceClassHasRolesAllowed && !resourceMethodHasPermitAll && !resourceClassHasPermitAll - - // a || ( b && !c ) || ( !a && !b && !c && !d) - - // (a || b || !d) && ( a || !c) return (resourceMethodHasRolesAllowed || resourceClassHasRolesAllowed || !resourceClassHasPermitAll) && (resourceMethodHasRolesAllowed || !resourceMethodHasPermitAll); } diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java index a4d720817..604246f48 100644 --- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JsonExceptionMapper.java @@ -54,9 +54,6 @@ public class JsonExceptionMapper implements @Context private HttpHeaders headers; - @Value("${microservice.show_request_uri:#{null}}") - private Boolean showRequestUri; - @Value("${microservice.show_stack_trace:#{null}}") private Boolean showStackTrace; @@ -105,12 +102,10 @@ private void log(final Throwable exception) { */ public void setContextData(final HttpHeaders headers, final UriInfo uriInfo, - final boolean showRequestUri, final boolean showStackTrace) { this.headers = headers; this.uriInfo = uriInfo; - this.showRequestUri = showRequestUri; this.showStackTrace = showStackTrace; } @@ -122,11 +117,9 @@ public void setContextData(final HttpHeaders headers, @PostConstruct public void setDebugFlags() { - if (showRequestUri == null) { - showRequestUri = LOG.isDebugEnabled(); - } if (showStackTrace == null) { showStackTrace = LOG.isDebugEnabled(); + LOG.debug("stack trace enabled if this is shown"); } } @@ -152,7 +145,7 @@ public Response toResponse(final Throwable exception) { .build(); } else { return Response.status(status) - .entity(new ErrorResponse(exception, headers, uriInfo, showStackTrace, showRequestUri)) + .entity(new ErrorResponse(exception, uriInfo, showStackTrace)) .type(mediaType) .build(); } diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java index 9f18d71ca..f5141cf24 100644 --- a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/JwtAssertionInterceptor.java @@ -2,7 +2,6 @@ import static net.trajano.ms.core.ErrorCodes.FORBIDDEN; import static net.trajano.ms.core.ErrorCodes.UNAUTHORIZED_CLIENT; -import static net.trajano.ms.core.Qualifiers.REQUEST_ID; import java.net.URI; import java.util.Arrays; @@ -22,17 +21,20 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; -import net.trajano.ms.vertx.beans.CachedDataProvider; import org.jose4j.jwk.HttpsJwks; import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.MalformedClaimException; import org.jose4j.jwt.consumer.InvalidJwtException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import net.trajano.ms.core.ErrorResponse; +import net.trajano.ms.spi.MDCKeys; +import net.trajano.ms.vertx.beans.CachedDataProvider; import net.trajano.ms.vertx.beans.DefaultAssertionRequiredPredicate; import net.trajano.ms.vertx.beans.JwtAssertionRequiredPredicate; import net.trajano.ms.vertx.beans.JwtClaimsProcessor; @@ -45,7 +47,7 @@ */ @Component @Provider -@Priority(Priorities.AUTHORIZATION) +@Priority(Priorities.AUTHORIZATION + 1) public class JwtAssertionInterceptor implements ContainerRequestFilter { @@ -59,6 +61,8 @@ public class JwtAssertionInterceptor implements private JwtAssertionRequiredPredicate assertionRequiredPredicate; + private CachedDataProvider cachedDataProvider; + private JwtClaimsProcessor claimsProcessor; @Autowired(required = false) @@ -70,8 +74,6 @@ public class JwtAssertionInterceptor implements */ private final ConcurrentMap jwks = new ConcurrentHashMap<>(); - private CachedDataProvider cachedDataProvider; - @Context private ResourceInfo resourceInfo; @@ -86,7 +88,7 @@ public void filter(final ContainerRequestContext requestContext) { LOG.warn("Missing assertion on request for {}", requestContext.getUriInfo()); requestContext.abortWith(Response.status(Status.UNAUTHORIZED) .header(HttpHeaders.WWW_AUTHENTICATE, X_JWT_ASSERTION) - .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "Missing assertion", requestContext.getHeaderString(REQUEST_ID))) + .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "Missing assertion")) .build()); return; } @@ -106,7 +108,9 @@ public void filter(final ContainerRequestContext requestContext) { } final List audience = Arrays.asList(requestContext.getHeaderString(X_JWT_AUDIENCE).split(", ")); claims = cachedDataProvider.buildConsumer(httpsJwks, audience).processToClaims(assertion); - } catch (final InvalidJwtException e) { + MDC.put(MDCKeys.JWT_ID, claims.getJwtId()); + } catch (final InvalidJwtException + | MalformedClaimException e) { if (LOG.isDebugEnabled()) { LOG.debug("JWT invalid", e); } else { @@ -114,7 +118,7 @@ public void filter(final ContainerRequestContext requestContext) { } requestContext.abortWith(Response.status(Status.UNAUTHORIZED) .header(HttpHeaders.WWW_AUTHENTICATE, X_JWT_ASSERTION) - .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "JWT was not valid", requestContext.getHeaderString(REQUEST_ID))) + .entity(new ErrorResponse(UNAUTHORIZED_CLIENT, "JWT was not valid")) .build()); return; } @@ -126,7 +130,7 @@ public void filter(final ContainerRequestContext requestContext) { if (!validateClaims) { LOG.warn("Validation of claims failed on request for {}", requestContext.getUriInfo()); requestContext.abortWith(Response.status(Status.FORBIDDEN) - .entity(new ErrorResponse(FORBIDDEN, "Claims validation failed", requestContext.getHeaderString(REQUEST_ID))) + .entity(new ErrorResponse(FORBIDDEN, "Claims validation failed")) .build()); } } @@ -154,16 +158,21 @@ public void setAssertionRequiredFunction(final JwtAssertionRequiredPredicate pre assertionRequiredPredicate = predicate; } + @Autowired + public void setCachedDataProvider(final CachedDataProvider cachedDataProvider) { + + this.cachedDataProvider = cachedDataProvider; + } + @Autowired(required = false) public void setClaimsProcessor(final JwtClaimsProcessor claimsProcessor) { this.claimsProcessor = claimsProcessor; } - @Autowired - public void setCachedDataProvider(final CachedDataProvider cachedDataProvider) { + public void setResourceInfo(final ResourceInfo resourceInfo) { - this.cachedDataProvider = cachedDataProvider; + this.resourceInfo = resourceInfo; } } diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java new file mode 100644 index 000000000..a6c8946c7 --- /dev/null +++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/MDCInterceptor.java @@ -0,0 +1,47 @@ +package net.trajano.ms.vertx.jaxrs; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import net.trajano.ms.spi.MDCKeys; +import net.trajano.ms.spi.MicroserviceEngine; + +/** + * This populates the MDC based on the data available on the request. This will + * skip the method and request URI if debug is not enabled. + * + * @author Archimedes Trajano + */ +@Component +@Provider +@Priority(Priorities.AUTHORIZATION) +public class MDCInterceptor implements + ContainerRequestFilter { + + private static final Logger LOG = LoggerFactory.getLogger(MDCInterceptor.class); + + @Autowired + private MicroserviceEngine engine; + + @Override + public void filter(final ContainerRequestContext requestContext) { + + MDC.put(MDCKeys.REQUEST_ID, requestContext.getHeaderString(MDCKeys.REQUEST_ID)); + if (LOG.isDebugEnabled()) { + MDC.put(MDCKeys.REQUEST_METHOD, requestContext.getMethod()); + MDC.put(MDCKeys.REQUEST_URI, requestContext.getUriInfo().getRequestUri().toASCIIString()); + MDC.put(MDCKeys.HOST, engine.hostname() + ":" + engine.port()); + } + + } + +} diff --git a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java index cd45b2618..a5816443c 100644 --- a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java +++ b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/ExceptionMapperTest.java @@ -47,7 +47,7 @@ public void setupMapper() { mapper.setDebugFlags(); final HttpHeaders headers = Mockito.mock(HttpHeaders.class); Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.WILDCARD_TYPE)); - mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true); + mapper.setContextData(headers, Mockito.mock(UriInfo.class), true); } @Test @@ -77,7 +77,7 @@ public void testCheckedHtml() { final HttpHeaders headers = Mockito.mock(HttpHeaders.class); Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.TEXT_HTML_TYPE)); - mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true); + mapper.setContextData(headers, Mockito.mock(UriInfo.class), true); final Response response = mapper.toResponse(new IOException("ahem")); Assert.assertEquals(500, response.getStatus()); @@ -97,7 +97,7 @@ public void testCheckedPlainText() { final HttpHeaders headers = Mockito.mock(HttpHeaders.class); Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.TEXT_PLAIN_TYPE)); - mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true); + mapper.setContextData(headers, Mockito.mock(UriInfo.class), true); final Response response = mapper.toResponse(new IOException("ahem")); Assert.assertEquals(500, response.getStatus()); @@ -110,7 +110,7 @@ public void testCheckedUnsupportedType() { final HttpHeaders headers = Mockito.mock(HttpHeaders.class); Mockito.when(headers.getAcceptableMediaTypes()).thenReturn(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM_TYPE)); - mapper.setContextData(headers, Mockito.mock(UriInfo.class), true, true); + mapper.setContextData(headers, Mockito.mock(UriInfo.class), true); final Response response = mapper.toResponse(new IOException("ahem")); Assert.assertEquals(500, response.getStatus()); diff --git a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java index 851d0594c..452b81cb2 100644 --- a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java +++ b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/InterceptorTest.java @@ -1,17 +1,26 @@ package net.trajano.ms.vertx.test; +import java.net.URI; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.UriInfo; + import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import net.trajano.ms.core.CryptoOps; +import net.trajano.ms.sample.Hello; import net.trajano.ms.vertx.VertxConfig; import net.trajano.ms.vertx.beans.JwtClaimsProcessor; import net.trajano.ms.vertx.jaxrs.JwtAssertionInterceptor; +import net.trajano.ms.vertx.jaxrs.MDCInterceptor; /** * Tests are hanging on Travis for some odd reason. @@ -53,16 +62,37 @@ public Boolean apply(final JwtClaims claims) { private CryptoOps cryptoOps; @Autowired - private JwtAssertionInterceptor interceptor; + private JwtAssertionInterceptor jwtInterceptor; + + @Autowired + private MDCInterceptor mdcInterceptor; + @SuppressWarnings({ + "unchecked", + "rawtypes" + }) @Test public void testInterceptor() throws Exception { - interceptor.setClaimsProcessor(new ValidatingProcessor("typ", "https://example.com/register")); + jwtInterceptor.setClaimsProcessor(new ValidatingProcessor("typ", "https://example.com/register")); - final JwtClaims jwtClaims = JwtClaims.parse("{\"typ\":\"https://example.com/register\"}"); + final JwtClaims jwtClaims = JwtClaims.parse("{\"typ\":\"https://example.com/register\", \"aud\":\"sample\", \"jti\": \"abc\", \"iss\":\"http://accounts.trajano.net\"}"); final String jwt = cryptoOps.sign(jwtClaims); System.out.println(jwt); + + final ResourceInfo resourceInfo = Mockito.mock(ResourceInfo.class); + Mockito.when(resourceInfo.getResourceMethod()).thenReturn(Hello.class.getMethod("hello2B")); + Mockito.when(resourceInfo.getResourceClass()).thenReturn((Class) Hello.class); + jwtInterceptor.setResourceInfo(resourceInfo); + + final ContainerRequestContext containerRequestContext = Mockito.mock(ContainerRequestContext.class); + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + Mockito.when(uriInfo.getRequestUri()).thenReturn(URI.create("http://trajano.net/sample")); + Mockito.when(containerRequestContext.getUriInfo()).thenReturn(uriInfo); + Mockito.when(containerRequestContext.getHeaderString("X-JWT-Assertion")).thenReturn(jwt); + Mockito.when(containerRequestContext.getHeaderString("X-JWT-Audience")).thenReturn("sample"); + mdcInterceptor.filter(containerRequestContext); + jwtInterceptor.filter(containerRequestContext); // final Request request = mock(Request.class); // when(request.getHeader("X-JWT-Assertion")).thenReturn(jwt); // final Response responder = mock(Response.class); diff --git a/ms-common/pom.xml b/ms-common/pom.xml index 7dbec9935..20ebf6b30 100644 --- a/ms-common/pom.xml +++ b/ms-common/pom.xml @@ -116,6 +116,11 @@ ${resteasy.version} test + + org.slf4j + slf4j-jdk14 + test + org.springframework.boot spring-boot-starter-test diff --git a/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java b/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java index f45009f2f..f273fa2c0 100644 --- a/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java +++ b/ms-common/src/main/java/net/trajano/ms/core/ErrorResponse.java @@ -1,6 +1,6 @@ package net.trajano.ms.core; -import static net.trajano.ms.core.Qualifiers.REQUEST_ID; +import static net.trajano.ms.spi.MDCKeys.REQUEST_ID; import java.net.URI; import java.util.LinkedList; @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import io.swagger.annotations.ApiModelProperty; +import net.trajano.ms.spi.MDCKeys; /** * Error response. This is the error response that gets built by the system for @@ -33,6 +34,8 @@ "requestId", "requestUri", "threadId", + "host", + "jwtId", "stackTrace", "cause" }) @@ -135,6 +138,18 @@ public String getMethodName() { @XmlElement(name = "error_description") private final String errorDescription; + /** + * Host of the server that threw the error. This shows the hostname and port. + */ + @XmlElement(name = "host") + private final String host; + + /** + * JWT ID. This represents the session. + */ + @XmlElement(name = "jwt_id") + private final String jwtId; + /** * The request ID. This is obtained from the header. */ @@ -175,6 +190,8 @@ protected ErrorResponse() { errorClass = null; errorDescription = null; stackTrace = null; + host = null; + jwtId = null; threadId = null; requestId = null; requestUri = null; @@ -192,22 +209,37 @@ protected ErrorResponse() { public ErrorResponse(final String error, final String errorDescription) { - this(error, errorDescription, MDC.get(REQUEST_ID)); - } - - public ErrorResponse(final String error, - final String errorDescription, - final String requestId) { - this.error = error; this.errorDescription = errorDescription; - this.requestId = requestId; + requestId = MDC.get(REQUEST_ID); cause = null; errorClass = null; + host = MDC.get(MDCKeys.HOST); + jwtId = MDC.get(MDCKeys.JWT_ID); stackTrace = null; threadId = Thread.currentThread().getName(); - requestUri = null; + requestUri = calculateRequestUri(); + } + + /** + * Creates an error response with just the error code and description. The + * request ID will be taken from the MDC. + * + * @param error + * error code + * @param errorDescription + * error description + * @param requestId + * ignored + * @deprecated the requestId value is ignored and is taken from the MDC. + */ + @Deprecated + public ErrorResponse(final String error, + final String errorDescription, + final String requestId) { + + this(error, errorDescription); } /** @@ -230,6 +262,8 @@ protected ErrorResponse(final Throwable e) { } } error = null; + host = null; + jwtId = null; errorClass = e.getClass().getName(); errorDescription = e.getMessage(); threadId = null; @@ -237,12 +271,47 @@ protected ErrorResponse(final Throwable e) { requestUri = null; } + /** + * Wraps a {@link Throwable} in an {@link ErrorResponse} with full stack trace + * and cause if requested. + * + * @param e + * exception to wrap + * @param headers + * ignored + * @param uriInfo + * URI info + * @param showStackTrace + * flag to determine whether the stack trace is to be shown. + * @param showRequestUri + * ignored + * @deprecated use {@link #ErrorResponse(Throwable, UriInfo, boolean)} + */ + @Deprecated public ErrorResponse(final Throwable e, final HttpHeaders headers, final UriInfo uriInfo, final boolean showStackTrace, final boolean showRequestUri) { + this(e, uriInfo, showStackTrace); + } + + /** + * Wraps a {@link Throwable} in an {@link ErrorResponse} with full stack trace + * and cause if requested. + * + * @param e + * exception to wrap + * @param uriInfo + * URI info + * @param showStackTrace + * flag to determine whether the stack trace is to be shown. + */ + public ErrorResponse(final Throwable e, + final UriInfo uriInfo, + final boolean showStackTrace) { + error = ErrorCodes.SERVER_ERROR; errorDescription = e.getLocalizedMessage(); errorClass = e.getClass().getName(); @@ -263,11 +332,29 @@ public ErrorResponse(final Throwable e, stackTrace = null; cause = null; } - requestId = headers.getHeaderString(REQUEST_ID); - requestUri = showRequestUri ? uriInfo.getRequestUri() : null; + requestId = MDC.get(REQUEST_ID); + host = MDC.get(MDCKeys.HOST); + requestUri = calculateRequestUri(); + jwtId = MDC.get(MDCKeys.JWT_ID); } + /** + * Performs a null-check on the request URI data. + * + * @return request URI + */ + private URI calculateRequestUri() { + + final String requestUriString = MDC.get(MDCKeys.REQUEST_URI); + if (requestUriString == null) { + + return null; + } else { + return URI.create(requestUriString); + } + } + public ErrorResponse getCause() { return cause; @@ -288,11 +375,26 @@ public String getErrorDescription() { return errorDescription; } + public String getHost() { + + return host; + } + + public String getJwtId() { + + return jwtId; + } + public String getRequestId() { return requestId; } + public URI getRequestUri() { + + return requestUri; + } + public List getStackTrace() { return stackTrace; diff --git a/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java b/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java index ad2638d37..faf9651b6 100644 --- a/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java +++ b/ms-common/src/main/java/net/trajano/ms/core/Qualifiers.java @@ -1,6 +1,7 @@ package net.trajano.ms.core; import net.trajano.ms.spi.CacheNames; +import net.trajano.ms.spi.MDCKeys; /** * Unclassified qualifiers used by the application. @@ -11,7 +12,7 @@ public final class Qualifiers { /** * JWKS Cache Name. - * + * * @deprecated use {@link net.trajano.ms.spi.CacheNames#JWKS} */ @Deprecated @@ -19,8 +20,11 @@ public final class Qualifiers { /** * Request ID HTTP Header. + * + * @deprecated use {@link net.trajano.ms.spi.MDCKeys#REQUEST_ID} */ - public static final String REQUEST_ID = "X-Request-ID"; + @Deprecated + public static final String REQUEST_ID = MDCKeys.REQUEST_ID; /** * Roles claim name. The claim is expected to be in a string list format. diff --git a/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java b/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java new file mode 100644 index 000000000..bc5427f5a --- /dev/null +++ b/ms-common/src/main/java/net/trajano/ms/spi/MDCKeys.java @@ -0,0 +1,41 @@ +package net.trajano.ms.spi; + +/** + * MDC Keys. These are used by the core API. The service implementation should + * populate the {@link org.slf4j.MDC} using the following keys + */ +public final class MDCKeys { + + /** + * Host name and port. + */ + public static final String HOST = "Host"; + + /** + * JWT ID. Represents a client session; + */ + public static final String JWT_ID = "X-JWT-ID"; + + /** + * Request ID. Represents a single request coming from the client. + */ + public static final String REQUEST_ID = "X-Request-ID"; + + /** + * Request method. Represents the method URI being requested upon. + */ + public static final String REQUEST_METHOD = "X-Request-Method"; + + /** + * Request URI. Represents the URI being requested. + */ + public static final String REQUEST_URI = "X-Request-URI"; + + /** + * Prevent instantiation of constants class. + */ + private MDCKeys() { + + } + +} diff --git a/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java b/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java index 7ee8455d7..e9680133e 100644 --- a/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java +++ b/ms-common/src/main/java/net/trajano/ms/spi/MicroserviceEngine.java @@ -10,4 +10,20 @@ public interface MicroserviceEngine { */ Object[] bootstrap(); + /** + * Gets the host name of where the engine is running. This may be + * null if the engine has not been initialized. + * + * @return host name + */ + String hostname(); + + /** + * Gets the port of where the engine is listening. This may be -1 + * if the engine has not been initialized. + * + * @return host name + */ + int port(); + } diff --git a/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java b/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java index 3978335fd..9e50646c5 100644 --- a/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java +++ b/ms-common/src/test/java/net/trajano/ms/common/test/ErrorResponseTest.java @@ -6,22 +6,25 @@ import static org.mockito.Mockito.mock; import java.io.IOException; +import java.net.URI; import java.security.GeneralSecurityException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; import org.junit.Test; +import org.slf4j.MDC; import org.springframework.beans.factory.BeanExpressionException; import net.trajano.ms.core.ErrorResponse; +import net.trajano.ms.spi.MDCKeys; public class ErrorResponseTest { @Test public void chainedError() { - final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(HttpHeaders.class), mock(UriInfo.class), true, false); + final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(UriInfo.class), true); assertNotNull(response.getStackTrace()); assertNotNull(response.getCause()); } @@ -32,7 +35,7 @@ public void chainedError() { @Test public void chainedError2() { - final ErrorResponse response = new ErrorResponse(new BeanExpressionException("ahem", new IllegalStateException(new GeneralSecurityException())), mock(HttpHeaders.class), mock(UriInfo.class), true, false); + final ErrorResponse response = new ErrorResponse(new BeanExpressionException("ahem", new IllegalStateException(new GeneralSecurityException())), mock(UriInfo.class), true); assertNotNull(response.getStackTrace()); assertNotNull(response.getCause()); } @@ -40,7 +43,7 @@ public void chainedError2() { @Test public void chainedErrorButNoStackTrace() { - final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(HttpHeaders.class), mock(UriInfo.class), false, false); + final ErrorResponse response = new ErrorResponse(new IOException("ahem", new IllegalStateException()), mock(UriInfo.class), false); assertNull(response.getStackTrace()); assertNull(response.getCause()); } @@ -48,30 +51,30 @@ public void chainedErrorButNoStackTrace() { @Test public void errorWithNoStackTraces() { - final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), false, false); + final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(UriInfo.class), false); assertNull(response.getStackTrace()); assertNull(response.getCause()); assertEquals(Thread.currentThread().getName(), response.getThreadId()); assertEquals(IOException.class.getName(), response.getErrorClass()); } + @Deprecated @Test - public void simpleError() { + public void thrownError() { - final ErrorResponse response = new ErrorResponse("error", "error description", "request id"); - assertEquals("error", response.getError()); - assertEquals("error description", response.getErrorDescription()); - assertEquals("request id", response.getRequestId()); - assertNull(response.getStackTrace()); + final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), true, false); + assertNotNull(response.getStackTrace()); assertNull(response.getCause()); } @Test - public void thrownError() { + public void thrownErrorWithMDC() { - final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(HttpHeaders.class), mock(UriInfo.class), true, false); + MDC.put(MDCKeys.REQUEST_URI, "http://hello"); + final ErrorResponse response = new ErrorResponse(new IOException("ahem"), mock(UriInfo.class), true); assertNotNull(response.getStackTrace()); assertNull(response.getCause()); + assertEquals(URI.create("http://hello"), response.getRequestUri()); } }