Open dhoffer opened 1 week ago
/cc @EricWittmann (openapi), @Ladicek (smallrye), @MikeEdgar (openapi), @jmartisk (smallrye), @phillip-kruger (openapi,smallrye), @radcortez (smallrye)
@dhoffer do you have the annotation @OpenApiFilter(OpenApiFilter.RunStage.RUN)
on the filter you want to run on each request?
This should work, can you provide a reproducer ?
@MikeEdgar No I had taken @OpenApiFilter off as I thought that might be conflicting with the quarkus.smallrye-openapi.always-run-filter property.
But I figured out what was happening. In our quarkus-maven-plugin configuration we do this in all projects:
`
`
For some reason, in just one project of many this caused the smallrye-openapi code to not include the application.yml in the list of ConfigSources for OpenApiDocumentService. I removed the above ignoredEntries item and then it works. No idea why it only fails to work in one app. I tried creating a reproducer with and without the above and it works in either case. I believe the above configuration is OBE and never needed as one can always override with an updated file at ./config/application.yml
So we now do have the OASFilter being called on each request but how now to pass in the user specific data per request? As that is the point of always-run-filter to make the results dynamic and based on user specific data, it just isn't clear how to use this flag.
You can do a CDI lookup to the class that holds this info.
@phillip-kruger I am not clear. We need to filter the resulting OpenAPI Json/Yaml so that it removes items the JAX-RS request user does not have permissions to have. We would get the user from the JWT in the request Header. Then we would lookup what the user can't see and then filter based on this data. We don't know how to get this data into our custom OASFilter implementation that does trigger on each request. But it doesn't know anything about the JAX-RS user that made the request to get his custom OpenAPI spec.
You can try an lookup io.quarkus.security.identity.SecurityIdentity
with CDI. Something like:
SecurityIdentity identity = Arc.container().instance(SecurityIdentity.class).get();
(This assumes you have secured the /q/openapi path as per your requirement)
@phillip-kruger Is there any documentation you can provide regarding integrating SecurityIdentity with custom OASFilter implementation? There are large gaps in my understanding of how this would work. We currently do not use any SecurityIdentity in Quarkus apps but just use JWT token. Currently our security is prior to the Quarkus apps and JWT provides user/roles/etc.
I don't think there is any documentation specifically on getting User info from an OASFilter. Let's make sure we are on that same page:
1) You have secured your REST services somehow (so example /api/*) is secured ? 2) You also secured your OpenAPI Schema (so /q/openapi) ? 3) In your OASFilter you can get the User (and roles) with Arc.container().instance(SecurityIdentity.class).get() ?
Once you have the user you can apply the rules you need and create the OpenAPI Schema as needed ? If you create a small app that does this I am happy to work with you to see if we can get this to work.
Yes for 1. All requests go through an external gateway and never see any Quarkus app unless authenticated. We use JWT to relay to Quarkus REST the user is authenticated.
Yes for 2 but this is new for us as we are upgrading from Quarkus 2.x to 3.x and also upgrading to use Microprofile OpenAPI. We were using Swagger libraries for OpenAPI. So in keeping with our REST layer security this will be the same. We will have a wrapper REST endpoint for this and we will get a JWT same as above. The /q/openapi endpoint will be private and never exposed through the gateway.
No for 3 as our app does not use SecurityIdentity. How can the OASFilter know about a particular user? Is it RequestScoped? We tried adding that and got build errors. Can we convert our JWT to SecurityIdentity and then get that via CDI in the OASFilter? It seems that could only work if our custom OASFilter is RequestScoped but I can't tell what the scope of our custom OASFilter is, all I can see is that I can get it to trigger on every request, which is a great first step. Now just have to figure out how to get the user's info in the OASFilter.
Btw, we are building off 999-SNAPSHOT to pickup some other non-released fixes we need for this upgrade.
We are trying to get to Quarkus 3.x with Microprofile OpenAPI but having a hard time getting there. Thanks for all your help!
We use JWT to relay to Quarkus REST the user is authenticated
How ?
Ok, so are you saying that the way that the user is getting the openapi schema is via REST ? And this Rest Endpoint is secure ? And this rest endpoint makes a call to /q/openapi to "proxy" the response ? And this REST endpoint has the JWT token (please explain how).
Are you using any of Quarkus Security ?
Can you maybe lookup org.eclipse.microprofile.jwt.JsonWebToken
with CDI ?
@phillip-kruger We have a front end 'appliance' that handles security, and it acts as a reverse proxy. No we don't use any of Quarkus security. We just pass a signed JWT in the REST headers. The scope of our changes are already very large, JDK21, Quarkus 3, Hibernate 6 & Microprofile OpenAPI so we can't also be adding security changes.
In the end, what we are doing is not ideal but it seems the best that can be done. We are wrapping the Microprofile OpenAPI inside our REST Layer. We then have to filter the output of Microprofile OpenAPI. It's not ideal but it works. We only call Microprofile OpenAPI once as what it makes is static.
We would like to see Microprofile OpenAPI changed so that it supports:
Hi @dhoffer. I am not sure how to help you here. We can definitely (in Quarkus) create a Schema (by running the filter) on every request. If you need any information in that filter (like the user details / jwt token) you can make those available as a RequestScoped object and look it up in the filter. Does this not work for you ?
Hi @phillip-kruger Yes that would help, it would need to be documented so we know how to do this. My main concern is that if we have to do ALL the schema generation on every request it might be too slow. 95% of the schema is the same on every request so best is to do the 95% once and do the 5% on every request.
My guess is that for most folks OpenAPI schema generation speed is not that that important as they are just viewing it in the Swagger UI or downloading the file for codegen. We have separate UI app(s) that collect this data from multiple Quarkus apps and those UIs let the user build custom queries from the OpenAPI schema so users can learn how to use our REST services. The extra delay is not desirable but may be tolerable.
As a comparision with our current Swagger library approach, it fully supports doing the 95% once and then we get the actual OpenAPI POJO object model where we can do our per user filtering that covers the other 5%. With our current workaround with the Microprofile OpenAPI approach we can't get the actual OpenAPI POJO object model we get the JSON or YAML string of the same. So it's a bit slower but I don't think anyone will notice but I wanted to explain the differences. We would stay with Swagger libraries but we want to create native Quarkus apps and those are problematic so trying to move our apps to fully Quarkus native compatable.
This would be documented in the CDI documentation: https://quarkus.io/guides/cdi
Basically Produce that data as a RequestScoped Object and use Arc.container().instance(YourObject.class).get() to get it.
@Produces
YourObject yourObject() {
YourObject yourObject = // ... here create an object that contains the user details
return yourObject;
}
You might have to add @RequestScope on the method
When running Runtime Filters, we do not recreate the whole schema document, we only apply your filter, so it not scanning annotations etc. every time . So it's doing what you want already.
@phillip-kruger I would be great if the microprofile-openapi documentation could be enhanced so it's clear to folks how to add @requestscope behaviors to the microprofile-openapi schema generation process. E.g. explain what parts are static, how the static part is handed to the @requestscope parts/etc. Ideally for us since OpenAPI schema generation is no different than any other REST operation we would like to pass the per request HttpHeaders & UriInfo already received via CDI into a @requestscope OpenAPI schema generation filter
Did my suggestion work ? I doubt that this will be documented in the MP Spec, but you are welcome to open a PR to update the Smallrye documentation in Quarkus. You might be able to lookup the HttpHeaders and UriInfo in the filter already. Have you tried that ?
Describe the bug
According to this link https://quarkus.io/guides/openapi-swaggerui#runtime-filters, it says that this property should cause the
quarkus.smallrye-openapi.always-run-filter = true filters to run on every request but it does not work. The filters only get called when the OpenAPI document is first requested.
We need the filters to run on each request as we need to customize the response based on the user's permissions which is in the Headers of the request so how can we get the above to work?
Expected behavior
Filters should run on each request per the provided documentation link.
Actual behavior
Filter only runs on first request.
How to Reproduce?
No response
Output of
uname -a
orver
No response
Output of
java -version
JDK21
Quarkus version or git rev
999-SNAPSHOT
Build tool (ie. output of
mvnw --version
orgradlew --version
)No response
Additional information
No response