OpenApi: Unable to override content type using OperationBuilderPlugin - spring-boot

I'm wasting a lot of time trying to figure out how to set a default MediaType for my Spring Controllers in the output of the api-docs generated by SpringFox (Docket openapi v3.0). Lastly I've found an almost undocumented interface OperationBuilderPlugin that theorically allows me to set some properties in the OperationContext but unfortunately though the property I'm looking for seems to be ignored when the operation is built:
#Override
public void apply(OperationContext context) {
context.operationBuilder()
.produces(new LinkedHashSet<>(
Collections.singletonList(MediaType.APPLICATION_JSON_VALUE)));
}
Also tried to set the produces directly into Docket but still no luck
#Bean
public Docket api() {
....
return new Docket(DocumentationType.OAS_30)
.securityContexts(Collections.singletonList(securityContext()))
.securitySchemes(Collections.singletonList(authenticationScheme))
.useDefaultResponseMessages(true)
.consumes(new HashSet<>(Arrays.asList(MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE)))
.produces(new LinkedHashSet<>(Arrays.asList("application/json", "application/xml")))
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
...
}
I don't want to specify the return MediaType at controller class level (or method level) so my last chance if I can't find a cleaner solution is to manually replace the */* with the mediatype i want after I download the api-docs.json from the remote url.
Does anybody ever had the same issue?
Any help would be much appreciated, thanks in advance

Managed to resolve by myself. In the end I've overridden the bean ResponseMessagesReader with another bean MyResponseMessagesReader that do almost the same of the original class, but in the method below I've changed the condition of produces that in the case of an empty set it adds the json media type
private void applyMyReturnTypeOverride(OperationContext context) {
...
if (produces.isEmpty()) {
produces.add(MediaType.APPLICATION_JSON);
}
...
}
This is the only joint point I was able to slip into the processo of operation creation, maybe there's a better way (implementing OperationBuilderPlugin with some neatest strategy) but my time was ticking out for this solution.
Hope this helps other people that needs the same API generation behaviour

Related

How to hide #Deprecated marked APIs/Controllers when generating Swagger via Docket?

I am looking for a way to hide the APIs marked as #Deprecated so they are not visible on the swagger UI.
One way is to use the hidden = true in the #Operation annotation but thats too manual and not easy to toggle back if I decide that I want to view the Deprecated endpoints again.
Is there another way that we can achieve this via Docket config?
#Operation(
tags = "Deprecated",
deprecated = true
)
#GetMapping("/api/customer/order")
public String viewOrders() {
//
}
#Bean
public Docket orderApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("order")
.apiInfo(metadata())
.select()
.paths(PathSelectors.ant("/order/**"))
.build();
}

How to diagnose/fix Signature Digest Verification Failure on web service response?

I am using wss4jSecurityInterceptor for validation and securement actions on my webservice. The SOAP messages back and forth are secured with Signature, Encryption and Timestamp. The problem I am having is, the recipient is unable to verify my response signature and gets the following error:
oracle.wsm.security.SecurityException: WSM-00061 : Signature digest verification failure. The system property xml.debug.verify should be enabled for the details about the digest calculations during verification phase (note xml.debug.verify slows down the signature verification for very large messages).
Here is the code that I am using to perform validation and securement actions:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
private static Log log = LogFactory.getLog(WebServiceConfig.class);
#Bean
public KeyStoreCallbackHandler securityCallbackHandler() {
KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler();
callbackHandler.setPrivateKeyPassword("changeit");
return callbackHandler;
}
#Bean
public Wss4jSecurityInterceptor securityInterceptor() throws Exception {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
// validate incoming request
securityInterceptor.setValidationActions("Timestamp Signature Encrypt");
securityInterceptor.setValidationSignatureCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationDecryptionCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationCallbackHandler(securityCallbackHandler());
// encrypt the response
securityInterceptor.setSecurementEncryptionUser("client_pub"); // alias of client public key
securityInterceptor.setSecurementEncryptionParts("{Content}{}Body");
securityInterceptor.setSecurementEncryptionKeyIdentifier("DirectReference");
securityInterceptor.setSecurementEncryptionCrypto(getCryptoFactoryBean().getObject());
// sign the response
securityInterceptor.setSecurementSignatureAlgorithm(WSS4JConstants.RSA_SHA1);
securityInterceptor.setSecurementSignatureDigestAlgorithm(WSS4JConstants.SHA1);
securityInterceptor.setSecurementSignatureKeyIdentifier("DirectReference");
securityInterceptor.setSecurementActions("Encrypt Signature Timestamp");
securityInterceptor.setSecurementUsername("secret"); // alias of the private key
securityInterceptor.setSecurementPassword("changeit");
securityInterceptor.setSecurementSignatureCrypto(getCryptoFactoryBean().getObject());
return securityInterceptor;
}
#Bean
public CryptoFactoryBean getCryptoFactoryBean() throws IOException {
CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
cryptoFactoryBean.setKeyStorePassword("changeit");
cryptoFactoryBean.setKeyStoreLocation(new ClassPathResource("cert/myKeystore.jks"));
return cryptoFactoryBean;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
try {
interceptors.add(securityInterceptor());
} catch (Exception e) {
throw new RuntimeException("could not initialize security interceptor");
}
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> registerMessageDispatcherServlet(
ApplicationContext context) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(context);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<MessageDispatcherServlet>(messageDispatcherServlet, "/ws/*");
}
The receiving side suspects that the error is caused by the fact that the certificate sent as has been corrupted by carriage return character sequence. Here is what it looks like:
<wsse:BinarySecurityToken
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
wsu:Id="X509-124b3a03-73e3-4838-a254-f20883ff4184">MIIG1zCCBb+gAwIBAgIRAIVi4APh2tBtAAAAAFDvSXwwDQYJKoZIhvcNAQELBQAwgboxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
 bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
 dGhvcml6ZWQgdXNlIG9ubHkxLjAsBgNVBAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBMMUswHhcNMTkwMzIyMTY1ODA5WhcNMjAwMzIyMTcyODA4WjCBgjELMAkGA1UEBhMCVVMx
 EzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCEJlcmtlbGV5MS4wLAYDVQQKEyVMYXdyZW5j
 ZSBCZXJrZWxleSBOYXRpb25hbCBMYWJvcmF0b3J5MRswGQYDVQQDExJtc2cudWNwLWl0LmxibC5n
b3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9fSfO1cKz1D34dQaQF1ScY0eEGALm
 s3hkRRWMYPWTH8gZRpfP4iFLF9Sx09vagGFHUrru1xf8BnVyB2CsZnvHtE+bDrK4kp4hQpO8aXqf
 VEpn+SpY38aQDfZUqjRK6HJM5qxF/2xjNKSw41MPkXALK04yga07IwSUE+GpPt8i2noQPKKJufzf
guA8Plmsnf9uNcpxFezNXC1k+HQBKeO0V+CZ4K5FKyckq64eT8VWbawF5CFoNigtbxTuHHClECV0
 VrzNNf5jj/cpymwOu51ljnPhGEY/f73DlEBZg8jpFcDSnAyYyJH2GFgz2wa0TiOpdxItTWHMXalb
 YtDH9VvRAgMBAAGjggMMMIIDCDAdBgNVHREEFjAUghJtc2cudWNwLWl0LmxibC5nb3YwggF/Bgor
BgEEAdZ5AgQCBIIBbwSCAWsBaQB2AFWB1MIWkDYBSuoLm1c8U/DA5Dh4cCUIFy+jqh0HE9MMAAAB
 aaZyHvYAAAQDAEcwRQIhAJtC0LBFOfupRDy6BOvRrH6pAlvAJFFUWxMbbSRV59nOAiB/c2D5VOwS
 aBeC4czjDjUAAfeunN/npVLyX7i6v69aLgB3AId1v+dZfPiMQ5lfvfNu/1aNR1Y2/0q1YMG06v9e
oIMPAAABaaZyHwQAAAQDAEgwRgIhAJj/g/g+UjWaoHCl7ldHG08zuUrL8XbAkR8Op4G+//nvAiEA
 sLoq29TrwnOtWa+O1+PRHCCllKD22yeKxdMUNrR2Pu0AdgCyHgXMi6LNiiBOh2b5K7mKJSBna9r6
 cOeySVMt74uQXgAAAWmmch7YAAAEAwBHMEUCIBVGxbR2c/ByOtfDRMlpIFPRYA5+dGkB4zJ7tjQ6
moYYAiEAmFbeJdg9rCZt6qUqhRH2mxJlotRjp+mZmiHyRFmulykwDgYDVR0PAQH/BAQDAgWgMB0G
 A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js
 LmVudHJ1c3QubmV0L2xldmVsMWsuY3JsMEsGA1UdIAREMEIwNgYKYIZIAYb6bAoBBTAoMCYGCCsG
AQUFBwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L3JwYTAIBgZngQwBAgIwaAYIKwYBBQUHAQEE
 XDBaMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBggrBgEFBQcwAoYnaHR0
 cDovL2FpYS5lbnRydXN0Lm5ldC9sMWstY2hhaW4yNTYuY2VyMB8GA1UdIwQYMBaAFIKicHTdvFM/
z3vU981/p2DGCky/MB0GA1UdDgQWBBRzakStBRG3O6NBdLt1XUkvwwKn4jAJBgNVHRMEAjAAMA0G
 CSqGSIb3DQEBCwUAA4IBAQAqt00w3oV4XgcdhcanJdgY/SUCtmN9I4jdras8vRson+wK6WJy6Em8
 EF5wyRDLOwUD75Rm9Ak23lKjYOcDTWQGG3YXrYh7xe3J8C+plEY1NAwNPXflCzxkDcdJ4pX0LDfj
aC5FniAvKJ9ET7ZQvpCjBRd7wgOrkuhf3YjCFC3hM4E6D7cGb6DEhCh9nOy+e9woQ/C1LnDil3NX
 Vgqk3PMZYkUeHjxqItnDcOAeJaeqsAUiTxZbC8sQQ9ae/CPahghgSWEwL7tMrAH4nGT3yeBQl0I9
 O7H9xWj0ooQ8Wt6nO7pq64X5uc7W6iA3/1Of5uCntmMrsqPlvSscP6QVuPw6</wsse:BinarySecurityToken>
My keystore is in PKCS format and I have also converted to jks. No dice. The certs in the keystore verify just fine with openssl command, so I know they are good.
I do my builds on a mac and deploy on CentOS. I have also copied the sources to CentOS and run the build there. Again, made no difference.
Has anyone run into this issue and solved it? Would appreciate any help!
Thanks much!
xmlsec library upgraded to 2.1.3.
wss4j-ws-security-dom upgraded to 2.2.3
Details: The problem was narrow and very obscure. I haven't put everything I did back to the way it was to see if the problem returns, but could do that easily. We use spring-ws-security and WSS4Jfrom apache to perform the validation and securement actions. Both libraries include xmlsec.jar which provides this functionality. In fact wss4j includes it twice since it also embeds opensaml , which in turn contains xmlsec. Spring-ws-security also contains its own xmlsec. They are all different versions. The problem with carriage returns is reported for xmlsec versions 2.1.0 and above.
https://issues.apache.org/jira/browse/SANTUARIO-482
https://issues.apache.org/jira/browse/SANTUARIO-494
I updated wss4j to ver 2.2.3 (where this is ostensibly fixed) and also added the fixed version of xmlsec.jar directly for good measure.
Hope it helps someone else out. :-)

How to correctly instantiate RestTemplate without leaking resources

Please I have the following bean definitions
#Bean
public RestTemplate produceRestTemplate(ClientHttpRequestFactory requestFactory){
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setErrorHandler(restTemplateErrorHandler);
return restTemplate;
}
#Bean
public ClientHttpRequestFactory createRequestFactory() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(maxTotalConn);
connectionManager.setDefaultMaxPerRoute(maxPerChannel);
RequestConfig config = RequestConfig.custom().setConnectTimeout(100000).build();
CloseableHttpClient httpClient = HttpClients.createDefault();
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
The code works well but the problem is that fortify flags the code above as being potentially problematic with the following
"The function createRequestFactory() sometimes
fails to release a socket allocated by createDefault() on line 141."
Please anyone with any ideas as to how to correctly do this without fortify raising alarms
Thanks in advance
I am pretty sure that you don't need to do anything. It looks to be a fortify issue that it might not be updated to this usage scenario. There is a mechanism to take exceptions when working with code analyzers - these tools are not always correct.
A Bit of Discussion
Imagine , you are using CloseableHttpClient in a scenario where there would be no #Bean or HttpComponentsClientHttpRequestFactory , then I would say that fortify is correct because that is the very intention of using a java.io.Closeable .
Spring beans are usually singleton with an intention of instance reuse so fortify should know that you are not creating multiple instances and close() method on AutoCloseable would be called when factory is destroyed at shutdown.
if you look at code of - org.springframework.http.client.HttpComponentsClientHttpRequestFactory , this is there.
/**
* Shutdown hook that closes the underlying
* {#link org.apache.http.conn.HttpClientConnectionManager ClientConnectionManager}'s
* connection pool, if any.
*/
#Override
public void destroy() throws Exception {
if (this.httpClient instanceof Closeable) {
((Closeable) this.httpClient).close();
}
}
Your fortify is looking at code in isolation and not in integrated way so its flagging.
Check this 2 points, for solving the problem.
If you never call the httpClient.close() method, sometime you can effectivlely
run out of socket.
If you're code call this method automatically somewhere there is no vuln and problem.
Anyway, this could be a FalsePositiv depending of the Version of the Java and Lib you use

Does CompletableFuture have a corresponding Local context?

In the olden days, we had ThreadLocal for programs to carry data along with the request path since all request processing was done on that thread and stuff like Logback used this with MDC.put("requestId", getNewRequestId());
Then Scala and functional programming came along and Futures came along and with them came Local.scala (at least I know the twitter Futures have this class). Future.scala knows about Local.scala and transfers the context through all the map/flatMap, etc. etc. functionality such that I can still do Local.set("requestId", getNewRequestId()); and then downstream after it has travelled over many threads, I can still access it with Local.get(...)
Soooo, my question is in Java, can I do the same thing with the new CompletableFuture somewhere with LocalContext or some object (not sure of the name) and in this way, I can modify Logback MDC context to store it in that context instead of a ThreadLocal such that I don't lose the request id and all my logs across the thenApply, thenAccept, etc. etc. still work just fine with logging and the -XrequestId flag in Logback configuration.
EDIT:
As an example. If you have a request come in and you are using Log4j or Logback, in a filter, you will set MDC.put("requestId", requestId) and then in your app, you will log many log statements line this:
log.info("request came in for url="+url);
log.info("request is complete");
Now, in the log output it will show this:
INFO {time}: requestId425 request came in for url=/mypath
INFO {time}: requestId425 request is complete
This is using a trick of ThreadLocal to achieve this. At Twitter, we use Scala and Twitter Futures in Scala along with a Local.scala class. Local.scala and Future.scala are tied together in that we can achieve the above scenario still which is very nice and all our log statements can log the request id so the developer never has to remember to log the request id and you can trace through a single customers request response cycle with that id.
I don't see this in Java :( which is very unfortunate as there are many use cases for that. Perhaps there is something I am not seeing though?
If you come across this, just poke the thread here
http://mail.openjdk.java.net/pipermail/core-libs-dev/2017-May/047867.html
to implement something like twitter Futures which transfer Locals (Much like ThreadLocal but transfers state).
See the def respond() method in here and how it calls Locals.save() and Locals.restort()
https://github.com/simonratner/twitter-util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala
If Java Authors would fix this, then the MDC in logback would work across all 3rd party libraries. Until then, IT WILL NOT WORK unless you can change the 3rd party library(doubtful you can do that).
My solution theme would be to (It would work with JDK 9+ as a couple of overridable methods are exposed since that version)
Make the complete ecosystem aware of MDC
And for that, we need to address the following scenarios:
When all do we get new instances of CompletableFuture from within this class? → We need to return a MDC aware version of the same rather.
When all do we get new instances of CompletableFuture from outside this class? → We need to return a MDC aware version of the same rather.
Which executor is used when in CompletableFuture class? → In all circumstances, we need to make sure that all executors are MDC aware
For that, let's create a MDC aware version class of CompletableFuture by extending it. My version of that would look like below
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class MDCAwareCompletableFuture<T> extends CompletableFuture<T> {
public static final ExecutorService MDC_AWARE_ASYNC_POOL = new MDCAwareForkJoinPool();
#Override
public CompletableFuture newIncompleteFuture() {
return new MDCAwareCompletableFuture();
}
#Override
public Executor defaultExecutor() {
return MDC_AWARE_ASYNC_POOL;
}
public static <T> CompletionStage<T> getMDCAwareCompletionStage(CompletableFuture<T> future) {
return new MDCAwareCompletableFuture<>()
.completeAsync(() -> null)
.thenCombineAsync(future, (aVoid, value) -> value);
}
public static <T> CompletionStage<T> getMDCHandledCompletionStage(CompletableFuture<T> future,
Function<Throwable, T> throwableFunction) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return getMDCAwareCompletionStage(future)
.handle((value, throwable) -> {
setMDCContext(contextMap);
if (throwable != null) {
return throwableFunction.apply(throwable);
}
return value;
});
}
}
The MDCAwareForkJoinPool class would look like (have skipped the methods with ForkJoinTask parameters for simplicity)
public class MDCAwareForkJoinPool extends ForkJoinPool {
//Override constructors which you need
#Override
public <T> ForkJoinTask<T> submit(Callable<T> task) {
return super.submit(MDCUtility.wrapWithMdcContext(task));
}
#Override
public <T> ForkJoinTask<T> submit(Runnable task, T result) {
return super.submit(wrapWithMdcContext(task), result);
}
#Override
public ForkJoinTask<?> submit(Runnable task) {
return super.submit(wrapWithMdcContext(task));
}
#Override
public void execute(Runnable task) {
super.execute(wrapWithMdcContext(task));
}
}
The utility methods to wrap would be such as
public static <T> Callable<T> wrapWithMdcContext(Callable<T> task) {
//save the current MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
setMDCContext(contextMap);
try {
return task.call();
} finally {
// once the task is complete, clear MDC
MDC.clear();
}
};
}
public static Runnable wrapWithMdcContext(Runnable task) {
//save the current MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
setMDCContext(contextMap);
try {
return task.run();
} finally {
// once the task is complete, clear MDC
MDC.clear();
}
};
}
public static void setMDCContext(Map<String, String> contextMap) {
MDC.clear();
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
}
Below are some guidelines for usage:
Use the class MDCAwareCompletableFuture rather than the class CompletableFuture.
A couple of methods in the class CompletableFuture instantiates the self version such as new CompletableFuture.... For such methods (most of the public static methods), use an alternative method to get an instance of MDCAwareCompletableFuture. An example of using an alternative could be rather than using CompletableFuture.supplyAsync(...), you can choose new MDCAwareCompletableFuture<>().completeAsync(...)
Convert the instance of CompletableFuture to MDCAwareCompletableFuture by using the method getMDCAwareCompletionStage when you get stuck with one because of say some external library which returns you an instance of CompletableFuture. Obviously, you can't retain the context within that library but this method would still retain the context after your code hits the application code.
While supplying an executor as a parameter, make sure that it is MDC Aware such as MDCAwareForkJoinPool. You could create MDCAwareThreadPoolExecutor by overriding execute method as well to serve your use case. You get the idea!
You can find a detailed explanation of all of the above here in a post about the same.

Dropwizard intercept bad json and return custom error message

I want to intercept a bad JSON input and return custom error messages using Dropwizard application. I followed the approach of defining a custom exception mapper as mentioned here : http://gary-rowe.com/agilestack/2012/10/23/how-to-implement-a-runtimeexceptionmapper-for-dropwizard/ . But it did not work for me. This same question has been asked here https://groups.google.com/forum/#!topic/dropwizard-user/r76Ny-pCveA but unanswered.
Any help would be highly appreciated.
My code below and I am registering it in dropwizard as environment.jersey().register(RuntimeExceptionMapper.class);
#Provider
public class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> {
private static Logger logger = LoggerFactory.getLogger(RuntimeExceptionMapper.class);
#Override
public Response toResponse(RuntimeException runtime) {
logger.error("API invocation failed. Runtime : {}, Message : {}", runtime, runtime.getMessage());
return Response.serverError().type(MediaType.APPLICATION_JSON).entity(new Error()).build();
}
}
Problem 1:
The exception being thrown by Jackson doesn't extends RuntimeException, but it does extend Exception. This doesn't matter though. (See Problem 2)
Problem 2:
DropwizardResourceConfig, registers it's own JsonProcessingExceptionMapper. So you should already see results similar to
{
"message":"Unrecognized field \"field\" (class d.s.h.c.MyClass),..."
}
Now if you want to override this, then you should create a more specific exception mapper. When working with exception mappers the most specific one will be chosen. JsonProcessingException is subclassed by JsonMappingException and JsonProcessingException, so you will want to create an exception mapper for each of these. Then register them. I am not sure how to unregister the Dropwizard JsonProcessingExceptionMapper, otherwise we could just create a mapper for JsonProcessingException, which will save us the hassle of create both.
Update
So you can remove the Dropwizard mapper, if you want, with the following
Set<Object> providers = environment.jersey().getResourceConfig().getSingletons();
Iterator it = providers.iterator();
while (it.hasNext()) {
Object val = it.next();
if (val instanceof JsonProcessingExceptionMapper) {
it.remove();
break;
}
}
Then you are free to use your own mapper, JsonProcessingException

Resources