OpenAPITools / OpenAPITools/openapi-generator

[BUG] [JAVA] In webclient, SecurityRequirement is not required

Open
#19,222 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
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

https://github.com/OpenAPITools/openapi-generator/issues?q=is%3Aissue+label%3A%22Client%3A+Java%22+security+

^ 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.