diff --git a/ms-common-impl/pom.xml b/ms-common-impl/pom.xml
index e2979abdd..714c221fb 100644
--- a/ms-common-impl/pom.xml
+++ b/ms-common-impl/pom.xml
@@ -9,7 +9,7 @@
ms-common-impl
Common Microservice Implementation
- 3.1.4.Final
+ 4.0.0.Beta2
false
1.7.16
@@ -37,10 +37,6 @@
net.jcip
jcip-annotations
-
- org.apache.commons
- commons-lang3
-
@@ -74,16 +70,27 @@
- org.apache.bval
- bval-jsr
+ org.eclipse
+ yasson
+ 1.0.1
+ runtime
+
+
+ org.glassfish
+ javax.el
+ runtime
+
+
+ org.glassfish
+ javax.json
1.1.2
runtime
-
-
- commons-logging
- commons-logging
-
-
+
+
+ org.hibernate
+ hibernate-validator
+ 6.0.8.Final
+ runtime
net.trajano.commons
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 7d44fae9d..20c0baec5 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
@@ -67,7 +67,7 @@ public class VertxMicroserviceEngine implements
* Sets the system properties and sets up the logger. {@inheritDoc}
*/
@Override
- public Object[] bootstrap() {
+ public Class>[] bootstrap() {
System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory");
@@ -76,7 +76,7 @@ public Object[] bootstrap() {
System.setProperty("logging.config", logbackFile.getAbsolutePath());
}
- return new Object[] {
+ return new Class>[] {
VertxConfig.class,
VertxMicroserviceEngine.class
};
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/BeanValidator.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/BeanValidator.java
new file mode 100644
index 000000000..9da73ee36
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/BeanValidator.java
@@ -0,0 +1,181 @@
+package net.trajano.ms.vertx.beans;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Valid;
+import javax.validation.Validator;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.validation.GeneralValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import net.trajano.ms.core.ConstraintViolationResponse;
+import net.trajano.ms.core.ErrorResponses;
+
+/**
+ * This performs validation checks on the request message. If the request
+ * message does not pass validation it will throw a bad request exception.
+ *
+ * @author Archimedes Trajano
+ */
+@Component
+public class BeanValidator implements
+ GeneralValidator {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(BeanValidator.class);
+
+ @Autowired
+ private Validator validator;
+
+ @Override
+ public void checkViolations(final HttpRequest request) {
+
+ // does nothing
+
+ }
+
+ /**
+ * Obtain the name from JAX-RS annotations. If the parameter is not annotated is
+ * uses "body" as the name.
+ *
+ * @param parameter
+ * parameter
+ * @return parameter name
+ */
+ private String getParameterName(final Parameter parameter) {
+
+ final String name;
+ if (parameter.getAnnotation(FormParam.class) != null) {
+ name = "form." + parameter.getAnnotation(FormParam.class).value();
+ } else if (parameter.getAnnotation(QueryParam.class) != null) {
+ name = "query." + parameter.getAnnotation(QueryParam.class).value();
+ } else if (parameter.getAnnotation(PathParam.class) != null) {
+ name = "path." + parameter.getAnnotation(PathParam.class).value();
+ } else if (parameter.getAnnotation(HeaderParam.class) != null) {
+ name = "header." + parameter.getAnnotation(HeaderParam.class).value();
+ } else if (parameter.isAnnotationPresent(Context.class)) {
+ name = parameter.getType().getName();
+ } else {
+ name = "body";
+ }
+ return name;
+ }
+
+ @Override
+ public boolean isMethodValidatable(final Method method) {
+
+ return true;
+ }
+
+ @Override
+ public boolean isValidatable(final Class> clazz) {
+
+ return true;
+ }
+
+ @Override
+ public void validate(final HttpRequest request,
+ final Object object,
+ final Class>... groups) {
+
+ LOG.debug("validate {} {}", request, object);
+
+ }
+
+ /**
+ * {@inheritDoc}. {@link Context} annotated parameters are skipped unless the
+ * {@link Valid} annotation is present.
+ */
+ @Override
+ public void validateAllParameters(final HttpRequest request,
+ final Object object,
+ final Method method,
+ final Object[] parameterValues,
+ final Class>... groups) {
+
+ LOG.debug("validateAllParameters {} {} {}", request, object, parameterValues);
+
+ final Map>> violationMap = new LinkedHashMap<>();
+ boolean hasViolation = false;
+ for (int i = 0; i < method.getParameterCount(); ++i) {
+
+ final Parameter parameter = method.getParameters()[i];
+ if (parameter.isAnnotationPresent(Context.class) && !parameter.isAnnotationPresent(Valid.class)) {
+ continue;
+ }
+
+ final Object value = parameterValues[i];
+
+ final String name = getParameterName(parameter);
+
+ final Set> violations;
+ if (value == null) {
+ if (parameter.isAnnotationPresent(NotNull.class)) {
+ throw ErrorResponses.invalidRequest("missing value for " + name);
+ } else {
+ continue;
+ }
+ } else {
+ violations = validator.validate(value);
+ }
+ if (violations.isEmpty()) {
+ continue;
+ }
+
+ violationMap.put(name, violations);
+ hasViolation = true;
+ }
+ if (hasViolation) {
+ throw new BadRequestException(Response.status(Status.BAD_REQUEST).entity(new ConstraintViolationResponse(violationMap)).build());
+ }
+
+ }
+
+ @Override
+ public void validateReturnValue(final HttpRequest request,
+ final Object object,
+ final Method method,
+ final Object returnValue,
+ final Class>... groups) {
+
+ LOG.debug("validateReturnValue {} {} {}", request, object, returnValue);
+
+ if (returnValue == null) {
+ if (method.isAnnotationPresent(NotNull.class)) {
+ throw ErrorResponses.internalServerError("unexpected null result");
+ } else {
+ return;
+ }
+ }
+
+ final Set> errors = validator.validate(returnValue);
+
+ if (!errors.isEmpty()) {
+ throw new InternalServerErrorException("result object validation failed", new ConstraintViolationException(errors));
+ }
+
+ }
+
+}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/ValidationProvider.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/ValidationProvider.java
new file mode 100644
index 000000000..359842b62
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/beans/ValidationProvider.java
@@ -0,0 +1,17 @@
+package net.trajano.ms.vertx.beans;
+
+import javax.validation.Validation;
+import javax.validation.Validator;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ValidationProvider {
+
+ @Bean
+ public Validator beanValidationValidator() {
+
+ return Validation.buildDefaultValidatorFactory().getValidator();
+ }
+}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/AbstractJsonExceptionMapper.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/AbstractJsonExceptionMapper.java
new file mode 100644
index 000000000..14c8afc5e
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/AbstractJsonExceptionMapper.java
@@ -0,0 +1,91 @@
+package net.trajano.ms.vertx.jaxrs;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+
+import net.trajano.ms.Microservice;
+
+public abstract class AbstractJsonExceptionMapper implements
+ ExceptionMapper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Microservice.class);
+
+ /**
+ * Supported media types.
+ */
+ private static final Set SUPPORTED_MEDIA_TYPES = new HashSet<>(Arrays.asList(
+ MediaType.APPLICATION_JSON_TYPE,
+ MediaType.APPLICATION_XML_TYPE,
+ MediaType.TEXT_XML_TYPE,
+ MediaType.TEXT_PLAIN_TYPE,
+ MediaType.TEXT_HTML_TYPE));
+
+ @Context
+ private HttpHeaders headers;
+
+ @Value("${microservice.show_stack_trace:#{null}}")
+ protected Boolean showStackTrace;
+
+ @Context
+ protected UriInfo uriInfo;
+
+ /**
+ * Determines the appropriate media type based on what is requested. If wildcard
+ * use JSON.
+ *
+ * @return media type appropriate for request
+ */
+ protected MediaType getAppropriateMediaType() {
+
+ final List acceptableMediaTypes = headers.getAcceptableMediaTypes();
+ for (final MediaType mediaType : acceptableMediaTypes) {
+ if (mediaType.equals(MediaType.WILDCARD_TYPE)) {
+ return MediaType.APPLICATION_JSON_TYPE;
+ } else if (SUPPORTED_MEDIA_TYPES.contains(mediaType)) {
+ return mediaType;
+ }
+ }
+ return MediaType.APPLICATION_JSON_TYPE;
+
+ }
+
+ /**
+ * This sets the context data so the mapper can be unit tested.
+ */
+ public void setContextData(final HttpHeaders headers,
+ final UriInfo uriInfo,
+ final boolean showStackTrace) {
+
+ this.headers = headers;
+ this.uriInfo = uriInfo;
+ this.showStackTrace = showStackTrace;
+
+ }
+
+ /**
+ * If the show request URI or show stack trace are not defined, it will default
+ * to whether the current logger is on debug mode or not.
+ */
+ @PostConstruct
+ public void setDebugFlags() {
+
+ if (showStackTrace == null) {
+ showStackTrace = LOG.isDebugEnabled();
+ LOG.debug("stack trace enabled if this is shown");
+ }
+ }
+
+}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/ConstraintViolationExceptionMapper.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/ConstraintViolationExceptionMapper.java
new file mode 100644
index 000000000..e99ffc518
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/ConstraintViolationExceptionMapper.java
@@ -0,0 +1,58 @@
+package net.trajano.ms.vertx.jaxrs;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+import net.trajano.ms.Microservice;
+import net.trajano.ms.core.ConstraintViolationResponse;
+
+@Configuration
+@Component
+@Provider
+@Produces({
+ MediaType.APPLICATION_JSON,
+ MediaType.APPLICATION_XML,
+ MediaType.TEXT_XML,
+ MediaType.TEXT_PLAIN
+})
+public class ConstraintViolationExceptionMapper extends AbstractJsonExceptionMapper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Microservice.class);
+
+ @Override
+ public Response toResponse(final ConstraintViolationException exception) {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.warn("uri={} message={}", uriInfo.getRequestUri(), exception.getMessage(), exception);
+ for (final ConstraintViolation> violation : exception.getConstraintViolations()) {
+ LOG.debug("message={} obj={} path={}", violation.getMessage(), violation.getRootBean(), violation.getPropertyPath());
+ }
+ } else {
+ LOG.warn("uri={} message={}", uriInfo.getRequestUri(), exception.getMessage());
+ }
+ final MediaType mediaType = getAppropriateMediaType();
+
+ if (mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE) || mediaType.isCompatible(MediaType.TEXT_HTML_TYPE)) {
+ return Response.status(Status.BAD_REQUEST)
+ .entity(exception.getMessage())
+ .type(mediaType)
+ .build();
+ } else {
+ return Response.status(Status.BAD_REQUEST)
+ .entity(new ConstraintViolationResponse(exception, uriInfo, showStackTrace))
+ .type(mediaType)
+ .build();
+ }
+ }
+
+}
diff --git a/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/GeneralValidatorContextResolver.java b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/GeneralValidatorContextResolver.java
new file mode 100644
index 000000000..f1148aa1a
--- /dev/null
+++ b/ms-common-impl/src/main/java/net/trajano/ms/vertx/jaxrs/GeneralValidatorContextResolver.java
@@ -0,0 +1,28 @@
+package net.trajano.ms.vertx.jaxrs;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.Provider;
+
+import org.jboss.resteasy.spi.validation.GeneralValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+@Provider
+@Produces(MediaType.WILDCARD)
+public class GeneralValidatorContextResolver implements
+ ContextResolver {
+
+ @Autowired
+ private GeneralValidator generalValidator;
+
+ @Override
+ public GeneralValidator getContext(final Class> type) {
+
+ return generalValidator;
+
+ }
+
+}
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 604246f48..bca699ba6 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
@@ -1,30 +1,20 @@
package net.trajano.ms.vertx.jaxrs;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.annotation.PostConstruct;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriInfo;
-import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
+import net.trajano.ms.Microservice;
import net.trajano.ms.core.ErrorResponse;
@Configuration
@@ -36,49 +26,9 @@
MediaType.TEXT_XML,
MediaType.TEXT_PLAIN
})
-public class JsonExceptionMapper implements
- ExceptionMapper {
-
- private static final Logger LOG = LoggerFactory.getLogger(JsonExceptionMapper.class);
-
- /**
- * Supported media types.
- */
- private static final Set SUPPORTED_MEDIA_TYPES = new HashSet<>(Arrays.asList(
- MediaType.APPLICATION_JSON_TYPE,
- MediaType.APPLICATION_XML_TYPE,
- MediaType.TEXT_XML_TYPE,
- MediaType.TEXT_PLAIN_TYPE,
- MediaType.TEXT_HTML_TYPE));
-
- @Context
- private HttpHeaders headers;
-
- @Value("${microservice.show_stack_trace:#{null}}")
- private Boolean showStackTrace;
-
- @Context
- private UriInfo uriInfo;
-
- /**
- * Determines the appropriate media type based on what is requested. If wildcard
- * use JSON.
- *
- * @return media type appropriate for request
- */
- private MediaType getAppropriateMediaType() {
-
- final List acceptableMediaTypes = headers.getAcceptableMediaTypes();
- for (final MediaType mediaType : acceptableMediaTypes) {
- if (mediaType.equals(MediaType.WILDCARD_TYPE)) {
- return MediaType.APPLICATION_JSON_TYPE;
- } else if (SUPPORTED_MEDIA_TYPES.contains(mediaType)) {
- return mediaType;
- }
- }
- return MediaType.APPLICATION_JSON_TYPE;
+public class JsonExceptionMapper extends AbstractJsonExceptionMapper {
- }
+ private static final Logger LOG = LoggerFactory.getLogger(Microservice.class);
/**
* Log the exception if it is not NotFoundException and only use warn if it is a
@@ -97,32 +47,6 @@ private void log(final Throwable exception) {
}
}
- /**
- * This sets the context data so the mapper can be unit tested.
- */
- public void setContextData(final HttpHeaders headers,
- final UriInfo uriInfo,
- final boolean showStackTrace) {
-
- this.headers = headers;
- this.uriInfo = uriInfo;
- this.showStackTrace = showStackTrace;
-
- }
-
- /**
- * If the show request URI or show stack trace are not defined, it will default
- * to whether the current logger is on debug mode or not.
- */
- @PostConstruct
- public void setDebugFlags() {
-
- if (showStackTrace == null) {
- showStackTrace = LOG.isDebugEnabled();
- LOG.debug("stack trace enabled if this is shown");
- }
- }
-
@Override
public Response toResponse(final Throwable exception) {
diff --git a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/SpringJaxrsHandlerTest.java b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/SpringJaxrsHandlerTest.java
index 0bcbe4cfc..f70e59d69 100644
--- a/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/SpringJaxrsHandlerTest.java
+++ b/ms-common-impl/src/test/java/net/trajano/ms/vertx/test/SpringJaxrsHandlerTest.java
@@ -65,7 +65,7 @@ public void testAsync() {
assertNotNull(engine);
final Response response = ClientBuilder.newClient().target(baseUri).path("/api/hello/async").request().get();
assertEquals(200, response.getStatus());
- assertTrue(response.readEntity(String.class).startsWith("{"));
+ assertTrue(response.readEntity(String.class).contains("accounts.google.com"));
}
diff --git a/ms-common/pom.xml b/ms-common/pom.xml
index 4171ac83b..f9bdc09c9 100644
--- a/ms-common/pom.xml
+++ b/ms-common/pom.xml
@@ -9,7 +9,7 @@
ms-common
Microservice Common
- 3.5.1.Final
+ 4.0.0.Beta3
@@ -39,17 +39,20 @@
javax.annotation
javax.annotation-api
- 1.2
javax.enterprise
cdi-api
1.2
+
+ javax.validation
+ validation-api
+
javax.ws.rs
javax.ws.rs-api
- 2.0.1
+ 2.1
org.bitbucket.b_c
@@ -121,11 +124,6 @@
jcl-over-slf4j
test
-
- org.slf4j
- slf4j-jdk14
- test
-
org.springframework.boot
spring-boot-starter-test
@@ -152,7 +150,7 @@
false
-
+
diff --git a/ms-common/src/main/java/net/trajano/ms/Microservice.java b/ms-common/src/main/java/net/trajano/ms/Microservice.java
index 9b21e1421..2bf3363dc 100644
--- a/ms-common/src/main/java/net/trajano/ms/Microservice.java
+++ b/ms-common/src/main/java/net/trajano/ms/Microservice.java
@@ -9,6 +9,7 @@
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.WebApplicationType;
import net.trajano.ms.spi.MicroserviceEngine;
@@ -79,14 +80,14 @@ public static void run(final Class extends Application> applicationClass,
}
Microservice.applicationClass = applicationClass;
- final Object[] bootstrapObjects = microserviceEngine.bootstrap();
- final Object[] sources = new Object[extraSources.length + bootstrapObjects.length];
+ final Class>[] bootstrapObjects = microserviceEngine.bootstrap();
+ final Class>[] sources = new Class>[extraSources.length + bootstrapObjects.length];
System.arraycopy(extraSources, 0, sources, 0, extraSources.length);
System.arraycopy(bootstrapObjects, 0, sources, extraSources.length, bootstrapObjects.length);
final SpringApplication springApplication = new SpringApplication(sources);
- springApplication.setWebEnvironment(false);
+ springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication
.setBannerMode(Mode.OFF);
springApplication.run(args);
diff --git a/ms-common/src/main/java/net/trajano/ms/core/ConstraintViolationResponse.java b/ms-common/src/main/java/net/trajano/ms/core/ConstraintViolationResponse.java
new file mode 100644
index 000000000..b38ee9bee
--- /dev/null
+++ b/ms-common/src/main/java/net/trajano/ms/core/ConstraintViolationResponse.java
@@ -0,0 +1,130 @@
+package net.trajano.ms.core;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Contraint violation response. This is the error response that is sent when
+ * there is a Bean Validation error on the input data.
+ *
+ * @author Archimedes Trajano
+ */
+@XmlRootElement
+@XmlType(propOrder = {
+ "error",
+ "errorDescription",
+ "errorClass",
+ "violations",
+ "requestId",
+ "requestUri",
+ "threadId",
+ "host",
+ "jwtId",
+ "stackTrace",
+ "cause"
+})
+@JsonInclude(Include.NON_NULL)
+public class ConstraintViolationResponse extends ErrorResponse {
+
+ @XmlAccessorType(XmlAccessType.PROPERTY)
+ @XmlType(propOrder = {
+ "message",
+ "messageTemplate",
+ "path"
+ })
+ @SuppressWarnings("unused")
+ private static class ConstraintViolationElement {
+
+ /**
+ * Prefix added to the path.
+ */
+ private final String pathPrefix;
+
+ /**
+ * The wrapped violation.
+ */
+ private final ConstraintViolation> violation;
+
+ ConstraintViolationElement(final ConstraintViolation> violation) {
+
+ this.violation = violation;
+ pathPrefix = "";
+ }
+
+ ConstraintViolationElement(final ConstraintViolation> violation,
+ final String pathPrefix) {
+
+ this.violation = violation;
+ this.pathPrefix = pathPrefix + ".";
+ }
+
+ public String getMessage() {
+
+ return violation.getMessage();
+ }
+
+ public String getPath() {
+
+ return pathPrefix + violation.getPropertyPath().toString();
+ }
+
+ public String getTemplate() {
+
+ return violation.getMessageTemplate();
+ }
+ }
+
+ /**
+ * The request ID. This is obtained from the header.
+ */
+ @ApiModelProperty(name = "violations",
+ value = "Constraint violations.")
+ @XmlElement(name = "violations")
+ private final List violations;
+
+ /**
+ * Wraps a {@link Throwable} in an {@link ErrorResponse} with full stack trace
+ * and cause if requested.
+ *
+ * @param e
+ * constraint violation exception to wrap
+ * @param uriInfo
+ * URI info
+ * @param showStackTrace
+ * flag to determine whether the stack trace is to be shown.
+ */
+ public ConstraintViolationResponse(final ConstraintViolationException e,
+ final UriInfo uriInfo,
+ final boolean showStackTrace) {
+
+ super(e, uriInfo, showStackTrace);
+ violations = StreamSupport.stream(e.getConstraintViolations().spliterator(), false).map(v -> new ConstraintViolationElement(v)).collect(Collectors.toList());
+
+ }
+
+ public ConstraintViolationResponse(final Map>> violationMap) {
+
+ super(ErrorCodes.INVALID_REQUEST, "constraints violated");
+ violations = new LinkedList<>();
+ violationMap.forEach((key,
+ violationSet) -> violationSet.forEach(v -> violations.add(new ConstraintViolationElement(v, key))));
+ }
+}
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 e9680133e..d099d06f0 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
@@ -4,11 +4,11 @@ public interface MicroserviceEngine {
/**
* Performs the initialization of the microservice engine and returns an array
- * of objects that would be used to bootstrap Spring.
+ * of classes that would be used to bootstrap Spring.
*
- * @return starting context objects.
+ * @return starting context classes.
*/
- Object[] bootstrap();
+ Class>[] bootstrap();
/**
* Gets the host name of where the engine is running. This may be
diff --git a/ms-common/src/main/javadoc/overview.html b/ms-common/src/main/javadoc/overview.html
index 8706254d6..aa5f32960 100644
--- a/ms-common/src/main/javadoc/overview.html
+++ b/ms-common/src/main/javadoc/overview.html
@@ -15,6 +15,7 @@
- JAX-RS
- CDI 1.2
+ - Bean Validation 2.0
- Annotations 1.2 (for
@PermitAll and
@RolesRequired annotations)
diff --git a/ms-common/src/test/java/net/trajano/ms/common/test/BootstrapTest.java b/ms-common/src/test/java/net/trajano/ms/common/test/BootstrapTest.java
index 72a83a8e4..97bc8a23a 100644
--- a/ms-common/src/test/java/net/trajano/ms/common/test/BootstrapTest.java
+++ b/ms-common/src/test/java/net/trajano/ms/common/test/BootstrapTest.java
@@ -37,7 +37,7 @@ public void setEngines(final MicroserviceEngine... engines) {
public void bootstrapTest() throws Exception {
final MicroserviceEngine microserviceEngine = Mockito.mock(MicroserviceEngine.class);
- Mockito.when(microserviceEngine.bootstrap()).thenReturn(new Object[] {
+ Mockito.when(microserviceEngine.bootstrap()).thenReturn(new Class>[] {
MyApp.class
});
new TestMicroservice().setEngines(microserviceEngine);
@@ -49,7 +49,7 @@ public void bootstrapTest() throws Exception {
public void doubleRunTest() throws Exception {
final MicroserviceEngine microserviceEngine = Mockito.mock(MicroserviceEngine.class);
- Mockito.when(microserviceEngine.bootstrap()).thenReturn(new Object[] {
+ Mockito.when(microserviceEngine.bootstrap()).thenReturn(new Class>[] {
MyApp.class
});
new TestMicroservice().setApplicationAndEngines(MyApp.class, microserviceEngine);
diff --git a/ms-engine-spring-jaxrs/pom.xml b/ms-engine-spring-jaxrs/pom.xml
index 46b530027..a78ad0bed 100644
--- a/ms-engine-spring-jaxrs/pom.xml
+++ b/ms-engine-spring-jaxrs/pom.xml
@@ -37,7 +37,7 @@
javax.ws.rs
javax.ws.rs-api
- 2.0.1
+ 2.1
org.slf4j
@@ -56,7 +56,7 @@
org.jboss.resteasy
resteasy-jaxrs
- 3.1.4.Final
+ 4.0.0.Beta2
test
diff --git a/ms-engine/pom.xml b/ms-engine/pom.xml
index d339f6b96..788d54e69 100644
--- a/ms-engine/pom.xml
+++ b/ms-engine/pom.xml
@@ -11,7 +11,7 @@
This is an implementation of a RESTful microservice engine
2.8.10
- 3.1.4.Final
+ 4.0.0.Beta2
1.7.16
@@ -79,7 +79,6 @@
javax.annotation
javax.annotation-api
- 1.2
javax.enterprise
@@ -89,7 +88,7 @@
javax.ws.rs
javax.ws.rs-api
- 2.0.1
+ 2.1
org.jboss.resteasy
@@ -134,6 +133,10 @@
org.jboss.spec.javax.ws.rs
jboss-jaxrs-api_2.0_spec
+
+ org.jboss.spec.javax.ws.rs
+ jboss-jaxrs-api_2.1_spec
+
@@ -153,6 +156,10 @@
org.jboss.spec.javax.ws.rs
jboss-jaxrs-api_2.0_spec
+
+ org.jboss.spec.javax.ws.rs
+ jboss-jaxrs-api_2.1_spec
+
@@ -185,6 +192,12 @@
+
+ io.vertx
+ vertx-codegen
+ ${vertx.version}
+ test
+
io.vertx
vertx-unit
diff --git a/ms-engine/src/main/java/net/trajano/ms/engine/SpringJaxRsHandler.java b/ms-engine/src/main/java/net/trajano/ms/engine/SpringJaxRsHandler.java
index b3480b37d..588d4234f 100644
--- a/ms-engine/src/main/java/net/trajano/ms/engine/SpringJaxRsHandler.java
+++ b/ms-engine/src/main/java/net/trajano/ms/engine/SpringJaxRsHandler.java
@@ -149,7 +149,6 @@ public SpringJaxRsHandler(final ConfigurableApplicationContext baseApplicationCo
applicationContext.addBeanFactoryPostProcessor(springBeanProcessor);
applicationContext.addApplicationListener(springBeanProcessor);
- applicationContext.refresh();
if (baseApplicationContext != null) {
baseApplicationContext.getBeansWithAnnotation(Provider.class).forEach(
(name,
@@ -160,6 +159,7 @@ public SpringJaxRsHandler(final ConfigurableApplicationContext baseApplicationCo
}
});
}
+ applicationContext.refresh();
applicationContext.getBeansWithAnnotation(Path.class).forEach((name,
obj) -> pathAnnotatedClasses.add(ClassUtils.getUserClass(obj)));
@@ -197,7 +197,6 @@ public void handle(final RoutingContext context) {
final ResteasyUriInfo uriInfo = new ResteasyUriInfo(serverRequest.absoluteURI(), serverRequest.query(), baseUri.toASCIIString());
final VertxHttpRequest request = new VertxHttpRequest(context, uriInfo, providerFactory);
-
context.request().setExpectMultipart(isMultipartExpected(request));
try (final VertxHttpResponse response = new VertxHttpResponse(context)) {
context.vertx().executeBlocking(
diff --git a/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxAsynchronousResponse.java b/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxAsynchronousResponse.java
index 4d7c792cc..84e0fb1b6 100644
--- a/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxAsynchronousResponse.java
+++ b/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxAsynchronousResponse.java
@@ -3,8 +3,8 @@
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -132,6 +132,25 @@ private void cancelTimer() {
}
}
+ @Override
+ public void complete() {
+
+ if (done.get()) {
+ return;
+ }
+ if (cancelled.get()) {
+ return;
+ }
+ done.compareAndSet(false, true);
+
+ }
+
+ @Override
+ public void completionCallbacks(final Throwable throwable) {
+
+ completionCallbacks.forEach(callback -> callback.onComplete(throwable));
+ }
+
@Override
public Annotation[] getAnnotations() {
@@ -190,6 +209,9 @@ public boolean isDone() {
return done.get();
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public boolean isSuspended() {
@@ -197,49 +219,58 @@ public boolean isSuspended() {
}
/**
- * Not used. {@inheritDoc}
- *
- * @return empty set.
+ * {@inheritDoc}
*/
@Override
- public Collection> register(final Class> callback) {
+ public Collection> register(final Class> callbackClass) {
- return Collections.emptySet();
+ final Object cb = providerFactory.createProviderInstance(callbackClass);
+ return register(cb);
}
/**
- * Not used. {@inheritDoc}
- *
- * @return empty map.
+ * {@inheritDoc}
*/
@Override
public Map, Collection>> register(final Class> callback,
final Class>... callbacks) {
- return Collections.emptyMap();
+ final Map, Collection>> map = new HashMap<>();
+ map.put(callback, register(callback));
+ for (final Class> other : callbacks) {
+ map.put(other, register(other));
+ }
+ return map;
}
/**
- * Not used. {@inheritDoc}
- *
- * @return empty set.
+ * {@inheritDoc}
*/
@Override
public Collection> register(final Object callback) {
- return Collections.emptySet();
+ final List> registered = new LinkedList<>();
+ if (callback instanceof CompletionCallback) {
+ completionCallbacks.add((CompletionCallback) callback);
+ registered.add(CompletionCallback.class);
+ }
+ return registered;
}
/**
- * Not used. {@inheritDoc}
- *
- * @return empty map.
+ * {@inheritDoc}
*/
@Override
public Map, Collection>> register(final Object callback,
final Object... callbacks) {
- return Collections.emptyMap();
+ final Map, Collection>> map = new HashMap<>();
+ map.put(callback.getClass(), register(callback));
+ for (final Object other : callbacks) {
+ map.put(other.getClass(), register(other));
+ }
+ return map;
+
}
/**
@@ -287,7 +318,6 @@ private boolean sendData(final Object entity) {
done.set(true);
cancelTimer();
- completionCallbacks.forEach(callback -> callback.onComplete(null));
return true;
} catch (final IOException e) {
throw new InternalServerErrorException(e);
@@ -373,7 +403,7 @@ public void setWriterInterceptors(final WriterInterceptor[] writerInterceptors)
*/
private void writeResponse(final Response response) throws IOException {
- ServerResponseWriter.writeNomapResponse((BuiltResponse) response, request, new VertxHttpResponse(routingContext), providerFactory);
+ ServerResponseWriter.writeNomapResponse((BuiltResponse) response, request, new VertxHttpResponse(routingContext), providerFactory, throwable -> completionCallbacks.forEach(c -> c.onComplete(throwable)), true);
}
}
diff --git a/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxHttpResponse.java b/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxHttpResponse.java
index ee22ff60f..70b12b204 100644
--- a/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxHttpResponse.java
+++ b/ms-engine/src/main/java/net/trajano/ms/engine/internal/resteasy/VertxHttpResponse.java
@@ -40,6 +40,13 @@ public void addNewCookie(final NewCookie cookie) {
}
+ @Override
+ public void flushBuffer() throws IOException {
+
+ os.flush();
+
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/ms-engine/src/test/java/net/trajano/ms/engine/test/SpringJaxRsHandlerTest.java b/ms-engine/src/test/java/net/trajano/ms/engine/test/SpringJaxRsHandlerTest.java
index d9d867faa..8693f3801 100644
--- a/ms-engine/src/test/java/net/trajano/ms/engine/test/SpringJaxRsHandlerTest.java
+++ b/ms-engine/src/test/java/net/trajano/ms/engine/test/SpringJaxRsHandlerTest.java
@@ -1,8 +1,8 @@
package net.trajano.ms.engine.test;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -18,7 +18,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Matchers;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import io.netty.util.AsciiString;
@@ -80,7 +80,7 @@ public void test400(final TestContext testContext) throws Exception {
when(response.headers()).thenReturn(new VertxHttpHeaders());
final Async async = testContext.async();
- when(response.setStatusCode(Matchers.any(Integer.class))).then(invocation -> {
+ when(response.setStatusCode(ArgumentMatchers.any(Integer.class))).then(invocation -> {
try {
return response;
@@ -152,7 +152,7 @@ public void test404Internal(final TestContext testContext) throws Exception {
when(response.headers()).thenReturn(new VertxHttpHeaders());
final Async async = testContext.async();
- when(response.setStatusCode(Matchers.any(Integer.class))).then(invocation -> {
+ when(response.setStatusCode(ArgumentMatchers.any(Integer.class))).then(invocation -> {
try {
return response;
@@ -189,7 +189,7 @@ public void test500(final TestContext testContext) throws Exception {
when(response.headers()).thenReturn(new VertxHttpHeaders());
final Async async = testContext.async();
- when(response.write(Matchers.any(Buffer.class))).thenReturn(response);
+ when(response.write(ArgumentMatchers.any(Buffer.class))).thenReturn(response);
doAnswer(i -> {
async.complete();
@@ -224,7 +224,7 @@ public void testFailure(final TestContext testContext) throws Exception {
when(response.headers()).thenReturn(new VertxHttpHeaders());
final Async async = testContext.async();
- when(response.setStatusCode(Matchers.any(Integer.class))).then(invocation -> {
+ when(response.setStatusCode(ArgumentMatchers.any(Integer.class))).then(invocation -> {
try {
return response;
@@ -261,7 +261,7 @@ public void testHandler(final TestContext testContext) throws Exception {
when(response.headers()).thenReturn(new VertxHttpHeaders());
final Async async = testContext.async();
- when(response.write(Matchers.any(Buffer.class))).then(invocation -> {
+ when(response.write(ArgumentMatchers.any(Buffer.class))).then(invocation -> {
try {
return response;
diff --git a/ms-gateway/pom.xml b/ms-gateway/pom.xml
index 222ea0c8d..c1720c864 100644
--- a/ms-gateway/pom.xml
+++ b/ms-gateway/pom.xml
@@ -76,6 +76,12 @@
jcl-over-slf4j
runtime
+
+ io.vertx
+ vertx-codegen
+ ${vertx.version}
+ test
+
net.trajano.commons
commons-testing
diff --git a/ms-gateway/src/main/java/net/trajano/ms/gateway/GatewayMS.java b/ms-gateway/src/main/java/net/trajano/ms/gateway/GatewayMS.java
index b28f3526e..e50b8c254 100644
--- a/ms-gateway/src/main/java/net/trajano/ms/gateway/GatewayMS.java
+++ b/ms-gateway/src/main/java/net/trajano/ms/gateway/GatewayMS.java
@@ -7,6 +7,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -28,7 +29,7 @@ public static void main(final String[] args) {
final SpringApplication application = new SpringApplication(GatewayMS.class);
application.setBannerMode(Mode.OFF);
- application.setWebEnvironment(false);
+ application.setWebApplicationType(WebApplicationType.NONE);
application.run(args);
}
diff --git a/ms-gateway/src/main/java/net/trajano/ms/gateway/handlers/GlobalFailureHandler.java b/ms-gateway/src/main/java/net/trajano/ms/gateway/handlers/GlobalFailureHandler.java
index b0157f640..615d6eb0a 100644
--- a/ms-gateway/src/main/java/net/trajano/ms/gateway/handlers/GlobalFailureHandler.java
+++ b/ms-gateway/src/main/java/net/trajano/ms/gateway/handlers/GlobalFailureHandler.java
@@ -4,9 +4,14 @@
import java.net.ConnectException;
import java.net.UnknownHostException;
+import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceResolvable;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@@ -26,14 +31,17 @@
@Order(SelfRegisteringRoutingContextHandler.CORE_GLOBAL)
public class GlobalFailureHandler extends SelfRegisteringRoutingContextHandler {
- private static final String GATEWAY_ERROR = "Gateway Error";
+ private static final MessageSourceResolvable GATEWAY_ERROR = new DefaultMessageSourceResolvable("err.gateway_error");
- private static final String GATEWAY_TIMEOUT = "Gateway Timeout";
+ private static final MessageSourceResolvable GATEWAY_TIMEOUT = new DefaultMessageSourceResolvable("err.gateway_timeout");
- private static final String INTERNAL_SERVER_ERROR = "Internal Server Error";
+ private static final MessageSourceResolvable INTERNAL_SERVER_ERROR = new DefaultMessageSourceResolvable("err.internal_server_error");
private static final Logger LOG = LoggerFactory.getLogger(GlobalFailureHandler.class);
+ @Autowired
+ private MessageSource r;
+
@Override
public void handle(final RoutingContext context) {
@@ -43,19 +51,19 @@ public void handle(final RoutingContext context) {
if (!context.response().ended()) {
if (context.failure() instanceof ConnectException) {
context.response().setStatusCode(504)
- .setStatusMessage(GATEWAY_TIMEOUT)
+ .setStatusMessage(r.getMessage(GATEWAY_TIMEOUT, Locale.getDefault()))
.putHeader(CONTENT_TYPE, MediaTypes.APPLICATION_JSON)
- .end(Errors.serverError(GATEWAY_TIMEOUT).toBuffer());
+ .end(Errors.serverError(r.getMessage(GATEWAY_TIMEOUT, Locale.getDefault())).toBuffer());
} else if (context.failure() instanceof UnknownHostException) {
context.response().setStatusCode(503)
- .setStatusMessage(GATEWAY_ERROR)
+ .setStatusMessage(r.getMessage(GATEWAY_ERROR, Locale.getDefault()))
.putHeader(CONTENT_TYPE, MediaTypes.APPLICATION_JSON)
- .end(Errors.serverError(GATEWAY_ERROR).toBuffer());
+ .end(Errors.serverError(r.getMessage(GATEWAY_ERROR, Locale.getDefault())).toBuffer());
} else {
context.response().setStatusCode(context.statusCode() == -1 ? 500 : context.statusCode())
- .setStatusMessage(INTERNAL_SERVER_ERROR)
+ .setStatusMessage(r.getMessage(INTERNAL_SERVER_ERROR, Locale.getDefault()))
.putHeader(HttpHeaders.CONTENT_TYPE, MediaTypes.APPLICATION_JSON)
- .end(Errors.serverError(INTERNAL_SERVER_ERROR).toBuffer());
+ .end(Errors.serverError(r.getMessage(INTERNAL_SERVER_ERROR, Locale.getDefault())).toBuffer());
}
}
diff --git a/ms-gateway/src/main/resources/messages.properties b/ms-gateway/src/main/resources/messages.properties
new file mode 100644
index 000000000..8c7bb400f
--- /dev/null
+++ b/ms-gateway/src/main/resources/messages.properties
@@ -0,0 +1,3 @@
+err.gateway_error=Gateway Error
+err.gateway_timeout=Gateway Timeout
+err.internal_server_error=Internal Server Error
diff --git a/ms-swagger/src/main/java/net/trajano/ms/swagger/internal/SwaggerCollator.java b/ms-swagger/src/main/java/net/trajano/ms/swagger/internal/SwaggerCollator.java
index f8c5d0104..38edb7e1b 100644
--- a/ms-swagger/src/main/java/net/trajano/ms/swagger/internal/SwaggerCollator.java
+++ b/ms-swagger/src/main/java/net/trajano/ms/swagger/internal/SwaggerCollator.java
@@ -142,15 +142,15 @@ private void processUris(final Map swagger,
int j = 0;
while (env.containsProperty(String.format("swagger[%d].uris[%d].swagger", i, j))) {
+ final URL swaggerUrl = env.getProperty(String.format("swagger[%d].uris[%d].swagger", i, j), URL.class);
try {
- final URL swaggerUrl = env.getProperty(String.format("swagger[%d].uris[%d].swagger", i, j), URL.class);
final Swagger remoteSwagger = io.swagger.util.Json.mapper().readerFor(Swagger.class).readValue(swaggerUrl.openConnection().getInputStream());
processPaths(swagger, definitionsMap, securityDefinitionsMap, remoteSwagger, i, j);
++j;
} catch (final IOException e) {
- throw new UncheckedIOException(e);
+ throw new UncheckedIOException("IOException processing " + swaggerUrl, e);
}
}
@@ -159,7 +159,7 @@ private void processUris(final Map swagger,
/**
* Update the current definition maps.
- *
+ *
* @param currentDefinitionsMap
* current definitions map
* @param currentSecurityDefinitionsMap
diff --git a/pom.xml b/pom.xml
index e95b717b3..9b9672731 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,7 @@
true
app-ms
1.7.16
- 1.5.10.RELEASE
+ 2.0.2.RELEASE
3.5.1
@@ -134,7 +134,7 @@
org.bitbucket.b_c
jose4j
- 0.6.1
+ 0.6.3
org.springframework.boot
diff --git a/sample-authz/clients-docker.json b/sample-authz/clients-docker.json
index 1a540a593..dc9a66c71 100644
--- a/sample-authz/clients-docker.json
+++ b/sample-authz/clients-docker.json
@@ -15,7 +15,7 @@
{
"client_id": "oidc_id",
"client_secret": "app_secret",
- "grant_types": ["refresh_token", "revocation", "urn:ietf:params:oauth:grant-type:jwt-bearer"],
+ "grant_types": ["openid", "refresh_token", "revocation", "urn:ietf:params:oauth:grant-type:jwt-bearer"],
"jwks_uri": "http://oidc-ms/.well-known/jwks",
"origin": "http://localhost:3000",
"redirect_uri": "http://localhost:3000/cb"
diff --git a/sample-ms/src/main/java/net/trajano/ms/example/HelloResource.java b/sample-ms/src/main/java/net/trajano/ms/example/HelloResource.java
index 3218e12ef..f7358d7f7 100644
--- a/sample-ms/src/main/java/net/trajano/ms/example/HelloResource.java
+++ b/sample-ms/src/main/java/net/trajano/ms/example/HelloResource.java
@@ -33,6 +33,7 @@
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -43,6 +44,7 @@
import io.swagger.annotations.Info;
import io.swagger.annotations.SwaggerDefinition;
import net.trajano.ms.core.JsonOps;
+import net.trajano.ms.example.beans.ContactCard;
import net.trajano.ms.example.domain.MyType;
@SwaggerDefinition(
@@ -72,6 +74,7 @@ public class HelloResource {
public void async(@Suspended final AsyncResponse asyncResponse) throws InterruptedException,
ExecutionException {
+ asyncResponse.register(new TestCallback());
final Future futureResponseFromClient = jaxrsClient.target("https://accounts.google.com/.well-known/openid-configuration").request().header(javax.ws.rs.core.HttpHeaders.USER_AGENT, "curl/7.55.1").async().get();
final Response responseFromClient = futureResponseFromClient.get();
@@ -92,6 +95,16 @@ public Response badClient() {
throw new BadRequestException("who's bad", Response.status(Status.BAD_REQUEST).entity(entity).build());
}
+ @ApiOperation(value = "Echos a validated JSON")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Validated
+ public ContactCard beanValidation(final ContactCard card) {
+
+ return card;
+ }
+
@ApiOperation(value = "throws a Runtime Exception")
@GET
@Path("/cough")
diff --git a/sample-ms/src/main/java/net/trajano/ms/example/TestCallback.java b/sample-ms/src/main/java/net/trajano/ms/example/TestCallback.java
new file mode 100644
index 000000000..db1774466
--- /dev/null
+++ b/sample-ms/src/main/java/net/trajano/ms/example/TestCallback.java
@@ -0,0 +1,22 @@
+package net.trajano.ms.example;
+
+import javax.ws.rs.container.CompletionCallback;
+import javax.ws.rs.ext.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Provider
+public class TestCallback implements
+ CompletionCallback {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TestCallback.class);
+
+ @Override
+ public void onComplete(final Throwable throwable) {
+
+ LOG.debug("Complete throwable={}", throwable, throwable);
+
+ }
+
+}
diff --git a/sample-ms/src/main/java/net/trajano/ms/example/beans/ContactCard.java b/sample-ms/src/main/java/net/trajano/ms/example/beans/ContactCard.java
new file mode 100644
index 000000000..7ea56b723
--- /dev/null
+++ b/sample-ms/src/main/java/net/trajano/ms/example/beans/ContactCard.java
@@ -0,0 +1,63 @@
+package net.trajano.ms.example.beans;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.validation.Valid;
+import javax.validation.constraints.DecimalMin;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Example with BeanValidation.
+ */
+@XmlRootElement
+public class ContactCard {
+
+ @XmlElement(name = "children")
+ @Valid
+ private final List children = new ArrayList<>();
+
+ private String fullName;
+
+ @DecimalMin(value = "1")
+ @NotNull
+ private Long id;
+
+ private String phone;
+
+ @NotNull(message = "{contact.wrong.name}")
+ public String getFullName() {
+
+ return fullName;
+ }
+
+ public Long getId() {
+
+ return id;
+ }
+
+ @Pattern(message = "{contact.wrong.phone}",
+ regexp = "[0-9]{3,9}")
+ public String getPhone() {
+
+ return phone;
+ }
+
+ public void setFullName(final String fullName) {
+
+ this.fullName = fullName;
+ }
+
+ public void setId(final Long id) {
+
+ this.id = id;
+ }
+
+ public void setPhone(final String phone) {
+
+ this.phone = phone;
+ }
+}
diff --git a/sample-oidc/application.yml b/sample-oidc/application.yml
index 0833b85ac..1d50fbbca 100644
--- a/sample-oidc/application.yml
+++ b/sample-oidc/application.yml
@@ -28,7 +28,7 @@ http:
issuer: http://oidc-ms
oidc:
- redirect_uri: https://localhost:3001/oidc/cb
+ redirect_uri: http://localhost:3001/oidc/cb
config_file: openidconnect-config.json
authorizationEndpoint: http://authorization-ms
diff --git a/src/test/artillery/load-test.yml b/src/test/artillery/load-test.yml
index 923af9f2f..b8a9f1926 100644
--- a/src/test/artillery/load-test.yml
+++ b/src/test/artillery/load-test.yml
@@ -10,8 +10,8 @@ config:
docker:
phases:
- duration: 60
- arrivalRate: 10
- target: "http://localhost:3001"
+ arrivalRate: 20
+ target: "http://192.168.1.113:3001"
variables:
origin: "http://localhost:3000"
prod:
diff --git a/src/test/postman/Docker.postman_environment.json b/src/test/postman/Docker.postman_environment.json
index f5e897813..1b4fc261d 100644
--- a/src/test/postman/Docker.postman_environment.json
+++ b/src/test/postman/Docker.postman_environment.json
@@ -1,34 +1,40 @@
{
- "id": "08bd1cb0-4131-ec31-0d5e-dc1d44ebb0f3",
+ "id": "a6b8dc56-0cec-4229-d03a-c2b60014590f",
"name": "Docker",
"values": [
{
"enabled": true,
"key": "access_token",
- "value": "R5Enmtic7QYQ6sRueTNekf4FAdJYpdzVgHfPfNi23Uah4xCyD8msw9VrYE5C7Q6v",
- "type": "text"
+ "type": "text",
+ "value": "nJNrdlaaxwPlRGuMbmr6uJh61SJUttbxiralvn2rQxQ0Yj1HXjALf8yaVXFOSGbt"
},
{
"enabled": true,
"key": "refresh_token",
- "value": "ZgftheeUK6LlQG6zanSHS8QvDQ2GsvSYo5K3EKw1Up0Kq1J7n36ZRqUGmrnoA5fL",
- "type": "text"
+ "type": "text",
+ "value": "rEbsQzhnJbHYYgyB9LNzlwTJ1eRJIZlS0JrCvJexONTgMUqSCN9LtkecKnjqYrBl"
},
{
"enabled": true,
"key": "client_credentials",
- "value": "YXBwX2lkOmFwcF9zZWNyZXQ=",
- "type": "text"
+ "type": "text",
+ "value": "YXBwX2lkOmFwcF9zZWNyZXQ="
},
{
"enabled": true,
"key": "base",
- "value": "http://localhost:3001",
- "type": "text"
+ "type": "text",
+ "value": "http://localhost:3001"
+ },
+ {
+ "enabled": true,
+ "key": "origin",
+ "type": "text",
+ "value": "http://localhost:3000"
}
],
- "timestamp": 1509336950561,
+ "timestamp": 1515373593852,
"_postman_variable_scope": "environment",
- "_postman_exported_at": "2017-10-30T04:16:17.739Z",
- "_postman_exported_using": "Postman/5.3.2"
+ "_postman_exported_at": "2018-01-08T01:12:06.323Z",
+ "_postman_exported_using": "Postman/5.5.0"
}
\ No newline at end of file
diff --git a/src/test/postman/Sample Test.postman_collection.json b/src/test/postman/Sample Test.postman_collection.json
index 6d2e84154..6064a2c52 100644
--- a/src/test/postman/Sample Test.postman_collection.json
+++ b/src/test/postman/Sample Test.postman_collection.json
@@ -1,682 +1,70 @@
{
- "variables": [],
"info": {
- "name": "Sample Test",
- "_postman_id": "65e18988-8cbb-ad32-a61a-79aaf6ac1b29",
- "description": "This validates the sample endpoints when they are running in a docker container.",
- "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
+ "name": "SA Mobile Enrolment",
+ "_postman_id": "f2d17678-17ab-813d-3243-a14e3b728d7f",
+ "description": "",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
- "name": "Authenticate",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "var jsonData = JSON.parse(responseBody)",
- "postman.setEnvironmentVariable(\"access_token\", jsonData.access_token)",
- "postman.setEnvironmentVariable(\"refresh_token\", jsonData.refresh_token)",
- "",
- "",
- "tests[\"Status code is 200\"] = responseCode.code === 200;",
- "",
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;"
- ]
- }
- }
- ],
+ "name": "Start Enrollment Process",
"request": {
- "url": "{{base}}/v1/authn",
"method": "POST",
"header": [
{
"key": "Content-Type",
- "value": "application/x-www-form-urlencoded",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Basic YXBwX2lkOmFwcF9zZWNyZXQ=",
- "description": ""
- },
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
+ "value": "application/json"
}
],
"body": {
- "mode": "urlencoded",
- "urlencoded": [
- {
- "key": "j_username",
- "value": "archie",
- "description": "",
- "type": "text"
- },
- {
- "key": "j_password",
- "value": "password",
- "description": "",
- "type": "text"
- }
- ]
+ "mode": "raw",
+ "raw": "{\n \"memberId\":\"merlene\",\n \"dateOfBirth\":\"1990-01-01\",\n \"targetState\": \"http://slashdot.org/\"\n}"
},
- "description": ""
- },
- "response": [
- {
- "id": "84f00471-1d78-45c5-9e6d-f41fbca66cd6",
- "name": "Authenticate as Archie",
- "originalRequest": {
- "url": "{{base}}/v1/authn",
- "method": "POST",
- "header": [
- {
- "key": "Content-Type",
- "type": "text",
- "name": "Content-Type",
- "value": "application/x-www-form-urlencoded",
- "description": ""
- },
- {
- "key": "Authorization",
- "type": "text",
- "name": "Authorization",
- "value": "Basic YXBwX2lkOmFwcF9zZWNyZXQ=",
- "description": ""
- },
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- }
- ],
- "body": {
- "mode": "urlencoded",
- "urlencoded": [
- {
- "key": "j_username",
- "value": "archie",
- "description": "",
- "type": "text"
- },
- {
- "key": "j_password",
- "value": "password",
- "description": "",
- "type": "text"
- }
- ]
- }
- },
- "status": "OK",
- "code": 200,
- "_postman_previewlanguage": "json",
- "_postman_previewtype": "text",
- "header": [
- {
- "name": "access-control-allow-origin",
- "key": "access-control-allow-origin",
- "value": "*",
- "description": "Specifies a URI that may access the resource. For requests without credentials, the server may specify '*' as a wildcard, thereby allowing any origin to access the resource."
- },
- {
- "name": "content-type",
- "key": "content-type",
- "value": "application/json",
- "description": "The mime type of this content"
- },
- {
- "name": "transfer-encoding",
- "key": "transfer-encoding",
- "value": "chunked",
- "description": "The form of encoding used to safely transfer the entity to the user. Currently defined methods are: chunked, compress, deflate, gzip, identity."
- },
- {
- "name": "x-request-id",
- "key": "x-request-id",
- "value": "Kih0UqMw",
- "description": "Custom header"
- }
+ "url": {
+ "raw": "http://localhost:8181/authentication-ms/oidc/enroll",
+ "protocol": "http",
+ "host": [
+ "localhost"
],
- "cookie": [],
- "responseTime": 80,
- "body": "{\"access_token\":\"lbs6hs8PqpbXDAxuMOyujmY2r8CterRqdxqgWSQm9EwHeb8yx6IuKqXxUbuQ6OHM\",\"expires_in\":300,\"refresh_token\":\"lxEFXHl7wUsdSwqMy4eVrhptv2SqeRmBwkANb03Z6xO2xWgIN966W7lWgWLLp9nQ\",\"token_type\":\"Bearer\"}"
- }
- ]
- },
- {
- "name": "displays hello world",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "var jsonData = JSON.parse(responseBody);",
- "tests[\"Principal is Archie\"] = jsonData.principal === \"internal-subject-archie\";"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/secure",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Refresh Token",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "var jsonData = JSON.parse(responseBody)",
- "postman.setEnvironmentVariable(\"access_token\", jsonData.access_token)",
- "postman.setEnvironmentVariable(\"refresh_token\", jsonData.refresh_token)",
- "",
- "",
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "tests[\"Content-Type is present\"] = postman.getResponseHeader(\"Content-Type\");",
- "",
- "tests[\"Transfer encoding is not present\"] = !postman.getResponseHeader(\"Transfer-Encoding\");"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/refresh",
- "method": "POST",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Content-Type",
- "value": "application/x-www-form-urlencoded",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Basic {{client_credentials}}",
- "description": ""
- }
- ],
- "body": {
- "mode": "urlencoded",
- "urlencoded": [
- {
- "key": "grant_type",
- "value": "refresh_token",
- "description": "",
- "type": "text"
- },
- {
- "key": "refresh_token",
- "value": "{{refresh_token}}",
- "description": "",
- "type": "text"
- }
- ]
- },
- "description": ""
- },
- "response": []
- },
- {
- "name": "displays hello world after refresh",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "var jsonData = JSON.parse(responseBody);",
- "tests[\"Principal is Archie\"] = jsonData.principal === \"internal-subject-archie\";"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/secure",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "displays openid config of google",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Body matches string\"] = responseBody.has(\"subject_types_supported\");",
- "",
- "tests[\"Response time is less than 1000ms\"] = responseTime < 1000;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/hello/openid",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Gets a JSON using GSON",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "var jsonData = JSON.parse(responseBody);",
- "tests[\"Hello world\"] = jsonData.Hello === \"world\";"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/hello/j",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Crash",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Body matches string\"] = responseBody.has(\"error\");",
- "",
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "tests[\"Status code is 500\"] = responseCode.code === 500;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/hello/cough",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Client Error",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Body matches string\"] = responseBody.has(\"error\");",
- "",
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "tests[\"Status code is 400\"] = responseCode.code === 400;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/hello/bad",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Upload files",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 500ms\"] = responseTime < 500;",
- "",
- "",
- "tests[\"Status code is 200\"] = responseCode.code === 200;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/hello/upload",
- "method": "POST",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {
- "mode": "formdata",
- "formdata": [
- {
- "key": "uploadedFile",
- "description": "",
- "type": "file",
- "src": "Localhost.postman_environment.json"
- },
- {
- "key": "uploadedFile",
- "description": "",
- "type": "file",
- "src": "Docker.postman_environment.json"
- }
+ "port": "8181",
+ "path": [
+ "authentication-ms",
+ "oidc",
+ "enroll"
]
},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Counts",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Status code is 200\"] = responseCode.code === 200;",
- "",
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/secure/count",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Get JWKS from Gateway",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "",
- "tests[\"Status code is 200\"] = responseCode.code === 200;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/jwks",
- "method": "GET",
- "header": [],
- "body": {},
- "description": ""
+ "description": "This is where the Member ID and Date of birth are specified."
},
"response": []
},
{
- "name": "Get Swagger From Gateway",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "",
- "tests[\"Status code is 200\"] = responseCode.code === 200;"
- ]
- }
- }
- ],
+ "name": "Validate PIN",
"request": {
- "url": "{{base}}/v1",
- "method": "GET",
- "header": [],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Logout",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "",
- "tests[\"Status code is 200\"] = responseCode.code === 200;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/logout",
- "method": "POST",
- "header": [
- {
- "key": "Authorization",
- "value": "Basic {{client_credentials}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Fail to access because logged out",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Response time is less than 200ms\"] = responseTime < 200;",
- "",
- "tests[\"Status code is 401\"] = responseCode.code === 401;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/v1/secure",
- "method": "GET",
- "header": [
- {
- "key": "Accept",
- "value": "application/json",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Bearer {{access_token}}",
- "description": ""
- }
- ],
- "body": {},
- "description": ""
- },
- "response": []
- },
- {
- "name": "Start Google Authentication",
- "event": [
- {
- "listen": "test",
- "script": {
- "type": "text/javascript",
- "exec": [
- "tests[\"Status code is 200\"] = responseCode.code === 200;"
- ]
- }
- }
- ],
- "request": {
- "url": "{{base}}/oidc/auth/google",
"method": "POST",
"header": [
{
"key": "Content-Type",
- "value": "application/x-www-form-urlencoded",
- "description": ""
- },
- {
- "key": "Authorization",
- "value": "Basic b2lkY19pZDphcHBfc2VjcmV0",
- "description": ""
+ "value": "application/json"
}
],
"body": {
- "mode": "urlencoded",
- "urlencoded": [
- {
- "key": "state",
- "value": "foo",
- "description": "",
- "type": "text"
- },
- {
- "key": "op",
- "value": "auth",
- "description": "",
- "type": "text"
- }
+ "mode": "raw",
+ "raw": "{\n \"token\":\"e_qNkp3L54tky3U5hdTAS7W8rngu\",\n \"pin\":\"FGZN\"\n}"
+ },
+ "url": {
+ "raw": "http://localhost:8181/api/oidc/validate_pin",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8181",
+ "path": [
+ "api",
+ "oidc",
+ "validate_pin"
]
},
- "description": ""
+ "description": "Validate PIN.\nThe token should be from the start enrollment process\nThe PIN should be visible in the Kube logs.\n\nCalling this will hang Postman because it is redirecting to SecureKey."
},
"response": []
}