OpenAPITools / OpenAPITools/openapi-generator
[BUG] [JAVA] In webclient, SecurityRequirement is not required
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator (example)?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
actual output has no required auth header required in the public interfaces of {name}Api.java. defining a securityscheme creates getters/setters. then securityRequirement creates a parser that will propagate the value from the setter if it is present.
This is the only thing added to {name}Api.java by the securityRequirement:
String[] localVarAuthNames = new String[] { "petstore_auth" };
to each route. This is then used later like this:
protected void updateParamsForAuth(String[] authNames, MultiValueMap<String, String> queryParams, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams) {
for (String authName : authNames) { // from localVarAuthNames
Authentication auth = authentications.get(authName); // this map is created during object init and codgen guarantees the securityRequirement auth methods are inserted to this map, it will never throw error
if (auth == null) {
throw new RestClientException("Authentication undefined: " + authName);
}
auth.applyToParams(queryParams, headerParams, cookieParams); // this is distinct per auth method with signature below
}
}
where applyToParams is an interface with this signature (note there is no exception or etc. compile time requirement for header presence):
public void applyToParams(MultiValueMap<String, String> queryParams, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams);
here is HttpBearerAuth.java's implementation of the interface as an example (they are all similar in webclient)
@Override
public void applyToParams(MultiValueMap<String, String> queryParams, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams) {
if (bearerToken == null) {
return;
}
headerParams.add(HttpHeaders.AUTHORIZATION, (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken);
}
I would expect a SecurityRequirement to be a compile time guarantee in the client because e.g. the auth header is not passed in parameters. This is exactly the case in "feign" see here:
the creation of auth in init
public void addAuthorization(String authName, RequestInterceptor authorization) {
if (apiAuthorizations.containsKey(authName)) {
throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations");
}
apiAuthorizations.put(authName, authorization);
feignBuilder.requestInterceptor(authorization);
}
then the requestInterceptor will throw an exception if auth parameters are null
public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
checkNotNull(username, "username");
checkNotNull(password, "password");
this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
}
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
Generated clients for any API which declare a global security requirement or route based security requirements, have no required auth at all. the requirement is simply present in the swagger UI, as well as is parsed on the client only if the auth is present, and exposes public getters/setters.
SecurityRequirement says as such the security is a requirement, so the expected outcome is the client should have compile time guarantee that the auth header be passed to the API public interfaces. e.g. just like a required header
openapi-generator version
6.6.0
7.7.0
OpenAPI declaration file content or url
{
"openapi": "3.0.0",
"servers": [
{
"url": "http://petstore.swagger.io/v2"
}
],
"info": {
"description": "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.",
"version": "1.0.0",
"title": "OpenAPI Petstore",
"license": {
"name": "Apache-2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"tags": [
{
"name": "pet",
"description": "Everything about your Pets"
},
{
"name": "store",
"description": "Access to Petstore orders"
},
{
"name": "user",
"description": "Operations about user"
}
],
"security": [
{
"api_key": []
}
],
"paths": {
"/pet": {
"post": {
"tags": [
"pet"
],
"summary": "Add a new pet to the store",
"description": "",
"operationId": "addPet",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
],
"requestBody": {
"$ref": "#/components/requestBodies/Pet"
}
}
}
},
"components": {
"requestBodies": {
"Pet": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
},
"description": "Pet object that needs to be added to the store",
"required": true
}
},
"securitySchemes": {
"petstore_auth": {
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
},
"schemas": {
"Order": {
"title": "Pet Order",
"description": "An order for a pets from the pet store",
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"petId": {
"type": "integer",
"format": "int64"
},
"quantity": {
"type": "integer",
"format": "int32"
},
"shipDate": {
"type": "string",
"format": "date-time"
},
"status": {
"type": "string",
"description": "Order Status",
"enum": [
"placed",
"approved",
"delivered"
]
},
"complete": {
"type": "boolean",
"default": false
}
},
"xml": {
"name": "Order"
}
},
"Category": {
"title": "Pet category",
"description": "A category for a pet",
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"
}
},
"xml": {
"name": "Category"
}
},
"User": {
"title": "a User",
"description": "A User who is purchasing from the pet store",
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"username": {
"type": "string"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},
"phone": {
"type": "string"
},
"userStatus": {
"type": "integer",
"format": "int32",
"description": "User Status"
}
},
"xml": {
"name": "User"
}
},
"Tag": {
"title": "Pet Tag",
"description": "A tag for a pet",
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
}
},
"xml": {
"name": "Tag"
}
},
"Pet": {
"title": "a Pet",
"description": "A pet for sale in the pet store",
"type": "object",
"required": [
"name",
"photoUrls"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"category": {
"$ref": "#/components/schemas/Category"
},
"name": {
"type": "string",
"example": "doggie"
},
"photoUrls": {
"type": "array",
"xml": {
"name": "photoUrl",
"wrapped": true
},
"items": {
"type": "string"
}
},
"tags": {
"type": "array",
"xml": {
"name": "tag",
"wrapped": true
},
"items": {
"$ref": "#/components/schemas/Tag"
}
},
"status": {
"type": "string",
"description": "pet status in the store",
"deprecated": true,
"enum": [
"available",
"pending",
"sold"
]
}
},
"xml": {
"name": "Pet"
}
},
"ApiResponse": {
"title": "An uploaded response",
"description": "Describes the result of uploading an image resource",
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}
Generation Details
java -jar $jar/openapi-generator-cli.jar generate
-i $input
--api-package com.linkedin.openhouse.$name.client.api
--model-package com.linkedin.openhouse.$name.client.model
--invoker-package com.linkedin.openhouse.$name.client.invoker
--group-id com.linkedin.openhouse.$name
--artifact-id generated-$name-client
--artifact-version 0.0.1-SNAPSHOT
-g java
-p java8=true
--library webclient
-o $output
Steps to reproduce
use my input above, the 6.6.0 jar, java8, and webclient library
Related issues/PRs
^ none of above cover this issue.
Suggest a fix
when SecurityRequirement is present,
any of:
- add the header to the parameters of public APIs / routes
- require getter/setter for auth to be called on ApiClient.java before being passed as parameter to [name]Api.java
- make applyToParams have an explicit parameter
- worst case make applyToParams throw exception if the auth value isn't present in the request.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by inspecting the generated {name}Api.java and HttpBearerAuth.java examples, focusing on updateParamsForAuth and applyToParams. Generate a Java WebClient client from the supplied Petstore specification and compare the public API with the required SecurityRequirement behavior; done means required authentication is enforced rather than silently omitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, openapi
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100