OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.91k stars 6.58k forks source link

[BUG] [Java] `Authentication undefined: my-auth-name` in ApiClient.java #19168

Open Felk opened 4 months ago

Felk commented 4 months ago

Bug Report Checklist

Description and Reproduction

Given an OpenAPI with a security schema of type openIdConnect, e.g.:

{
  "openapi" : "3.0.3",
  "info" : {
    "title" : "My API",
    "version" : "1.0.0"
  },
  "security" : [ {
    "Keycloak" : [ ]
  } ],
  "paths" : {
    "/api/stuff" : {
      "post" : {
        "summary" : "Does something important.",
        "responses" : {
          "200" : {
            "description" : "Everything went well"
          }
        },
        "security" : [ {
          "Keycloak" : [ "api_access" ]
        } ]
      }
    }
  },
  "components" : {
    "securitySchemes" : {
      "Keycloak" : {
        "type" : "openIdConnect",
        "description" : "This service is secured through OIDC, implemented by Keycloak",
        "openIdConnectUrl" : "https://auth.example.com/realms/myrealm/.well-known/openid-configuration"
      }
    }
  }
}

and a generated Java client, e.g. through the openapi-generator maven plugin in a spring boot project:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.7.0</version>
    <executions>
        <execution>
            <id>generate-my-client</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.build.directory}/my-openapi.json</inputSpec>
                <generatorName>java</generatorName>
                <library>webclient</library>
                <invokerPackage>com.example.myclient.invoker</invokerPackage>
                <apiPackage>com.example.myclient.api</apiPackage>
                <modelPackage>com.example.myclient.model</modelPackage>

                <configOptions>
                    <openApiNullable>false</openApiNullable>
                    <sourceFolder>myapi</sourceFolder>
                    <useSpringBoot3>true</useSpringBoot3>
                    <useJakartaEe>true</useJakartaEe>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

invocation fails with the following error:

org.springframework.web.client.RestClientException: Authentication undefined: Keycloak
    at com.example.myclient.invoker.ApiClient.updateParamsForAuth(ApiClient.java:701)
    at com.example.myclient.invoker.ApiClient.prepareRequest(ApiClient.java:622)
    at com.example.myclient.invoker.ApiClient.invokeAPI(ApiClient.java:582)
    at com.example.myclient.api.MergedApi.searchMarktrollenRequestCreation(MergedApi.java:8023)
    at com.example.myclient.api.MergedApi.searchMarktrollen(MergedApi.java:8039)
    at com.example.MyService.doClientCall(MyService.java:XY)
    [...]

which is emitted from this generated code:

protected void updateParamsForAuth(String[] authNames, MultiValueMap<String, String> queryParams, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams) {
    for (String authName : authNames) {
        Authentication auth = authentications.get(authName);
        if (auth == null) {
            throw new RestClientException("Authentication undefined: " + authName);
        }
        auth.applyToParams(queryParams, headerParams, cookieParams);
    }
}
openapi-generator version and Related issues/PRs

I tested 6.6.0, 7.5.0 and 7.7.0. It worked with 6.5.0 and is "broken" since the initial OIDC support landed in 6.6.0: https://github.com/OpenAPITools/openapi-generator/pull/15417

Investigation and suggested fix

Previous to the initial OIDC support, source generation would just output the following error:

[ERROR] Unknown type `openIdConnect` found in the security definition `null`.

This also caused localVarAuthNames in api/MyApi.java to generate as follows:

String[] localVarAuthNames = new String[] {  };

Starting with 6.6.0, the above error is no longer emitted during generation, and instead this code is emitted:

String[] localVarAuthNames = new String[] { "Keycloak" };

In another generated file, the invoker/ApiClient.java, the init method is generated as follows for all versions I tested:

protected void init() {
    // Setup authentications (key: authentication name, value: authentication).
    authentications = new HashMap<String, Authentication>();
    // Prevent the authentications from being modified.
    authentications = Collections.unmodifiableMap(authentications);
}

The respective mustache file ApiClient.mustache currently looks like this:

// Setup authentications (key: authentication name, value: authentication).
authentications = new HashMap<String, Authentication>();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}}
authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}}
authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}
authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}}
authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}
// Prevent the authentications from being modified.
authentications = Collections.unmodifiableMap(authentications);

I believe there is a case for isOpenIdConnect missing in this template. I don't know how easily it could be added, or whether there needs to be some fallback mechanism for authentication mechanisms that are not supported by some code generators.

Felk commented 4 months ago

I've created a small testcase that showcases a missing authentications entry in the ApiClient:

Subject: [PATCH] create test for issue 19168 regarding OIDC support in java CodeGen
---
Index: modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java  (revision 304ff965776ea7c7c16e798ae1b82e31e01b5afa)
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java  (date 1721050184687)
@@ -2955,4 +2955,24 @@

     }

+    @Test
+    void openidConnect_generates_authentications_entry_issue_19168() {
+        final CodegenConfigurator configurator = new CodegenConfigurator()
+                .setGeneratorName("java")
+                .setLibrary(WEBCLIENT)
+                .addGlobalProperty(CodegenConstants.MODEL_DOCS, "false")
+                .addGlobalProperty(CodegenConstants.MODEL_TESTS, "false")
+                .setInputSpec("src/test/resources/3_1/java/issue_19168_openidconnect.json")
+                .setOutputDir(newTempFolder().toString().replace("\\", "/"));
+
+        List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
+
+        assertThat(files)
+                .filteredOn(f -> f.getName().endsWith("ApiClient.java"))
+                .hasSize(1).first(FILE).content()
+                .contains(
+                        "authentications = new HashMap<String, Authentication>();",
+                        "authentications.put(\"Keycloak\", new OpenIdConnectAuth());"
+                );
+    }
 }
Index: modules/openapi-generator/src/test/resources/3_1/java/issue_19168_openidconnect.json
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/modules/openapi-generator/src/test/resources/3_1/java/issue_19168_openidconnect.json b/modules/openapi-generator/src/test/resources/3_1/java/issue_19168_openidconnect.json
new file mode 100644
--- /dev/null   (date 1721049746320)
+++ b/modules/openapi-generator/src/test/resources/3_1/java/issue_19168_openidconnect.json  (date 1721049746320)
@@ -0,0 +1,34 @@
+{
+  "openapi" : "3.0.3",
+  "info" : {
+    "title" : "My API",
+    "version" : "1.0.0"
+  },
+  "security" : [ {
+    "Keycloak" : [ ]
+  } ],
+  "paths" : {
+    "/api/stuff" : {
+      "post" : {
+        "summary" : "Does something important.",
+        "responses" : {
+          "200" : {
+            "description" : "Everything went well"
+          }
+        },
+        "security" : [ {
+          "Keycloak" : [ "api_access" ]
+        } ]
+      }
+    }
+  },
+  "components" : {
+    "securitySchemes" : {
+      "Keycloak" : {
+        "type" : "openIdConnect",
+        "description" : "This service is secured through OIDC, implemented by Keycloak",
+        "openIdConnectUrl" : "https://auth.example.com/realms/myrealm/.well-known/openid-configuration"
+      }
+    }
+  }
+}
Felk commented 4 months ago

As a workaround in the meantime: Does anyone know how I could disable authentication altogether? The OpenAPI I'm consuming does have OIDC, but it's handled on a different layer so I don't actually need openapi-generator's support for it right now. The only workarounds I found so far was either downgrading to 6.5, or overriding the API template to hardcode localVarAuthNames to an empty array

wing328 commented 4 months ago

@Felk what about removing security/auth setting in the spec as a workaround?

Felk commented 4 months ago

@wing328 that's a good suggestion. Unfortunately it doesn't quite work for us since we're currently downloading and processing the OpenAPI in a build step and therefore don't have a chance to manually modify it in-between. We also can't remove the authentication from the source since other consumers of the OpenAPI spec rely on the authentication to be there.

MaurizioCasciano commented 4 months ago

Same error for me with this OIDC security schema:

  securitySchemes:
    OIDC-Auth:
      bearerFormat: jwt
      flows:
        authorizationCode:
          authorizationUrl: https://sso.project-io.eu/realms/REALM/protocol/openid-connect/auth
          scopes:
            email: email
            openid: openid
            profile: profile
          tokenUrl: https://sso.project-io.eu/realms/REALM/protocol/openid-connect/token
      in: header
      name: OIDC-Auth
      openIdConnectUrl: https://sso.project-io.eu/realms/REALM/.well-known/openid-configuration
      scheme: bearer
      type: openIdConnect

and this security section:

security:
- OIDC-Auth: []

Can I do something on the Java consumer side to bypass this error until this issue gets fixed ?