diff --git a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java index 4253357f..35a36f9c 100644 --- a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java +++ b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java @@ -24,6 +24,12 @@ public class TokenPropagationResource { @RestClient org.acme.externalservice5.api.DefaultApi defaultApi5; + @RestClient + org.acme.externalservice6.api.DefaultApi defaultApi6; + + @RestClient + org.acme.externalservice7.api.DefaultApi defaultApi7; + @POST @Path("service1") public Response service1() { @@ -53,4 +59,16 @@ public Response service4() { public Response service5() { return defaultApi5.executeQuery5(); } + + @POST + @Path("service6") + public Response service6() { + return defaultApi6.executeQuery6(); + } + + @POST + @Path("service7") + public Response service7() { + return defaultApi7.executeQuery7(); + } } diff --git a/client/integration-tests/security/src/main/openapi/token-propagation-external-service6-with-base-url.yaml b/client/integration-tests/security/src/main/openapi/token-propagation-external-service6-with-base-url.yaml new file mode 100644 index 00000000..e2a47828 --- /dev/null +++ b/client/integration-tests/security/src/main/openapi/token-propagation-external-service6-with-base-url.yaml @@ -0,0 +1,19 @@ +--- +openapi: 3.0.3 +info: + title: token-propagation-external-service6-with-base-url API + version: 3.0.0-lts-SNAPSHOT +paths: + /token-propagation-external-service6-with-base-url/executeQuery6: + post: + operationId: executeQuery6 + responses: + "200": + description: OK + security: + - service6-http-bearer: [] +components: + securitySchemes: + service6-http-bearer: + type: http + scheme: bearer \ No newline at end of file diff --git a/client/integration-tests/security/src/main/openapi/token-propagation-external-service7-with-base-url.yaml b/client/integration-tests/security/src/main/openapi/token-propagation-external-service7-with-base-url.yaml new file mode 100644 index 00000000..aebe33d5 --- /dev/null +++ b/client/integration-tests/security/src/main/openapi/token-propagation-external-service7-with-base-url.yaml @@ -0,0 +1,19 @@ +--- +openapi: 3.0.3 +info: + title: token-propagation-external-service7-with-base-url API + version: 3.0.0-lts-SNAPSHOT +paths: + /token-propagation-external-service7-with-base-url/executeQuery7: + post: + operationId: executeQuery7 + responses: + "200": + description: OK + security: + - service7-http-bearer: [] +components: + securitySchemes: + service7-http-bearer: + type: http + scheme: bearer \ No newline at end of file diff --git a/client/integration-tests/security/src/main/resources/application.properties b/client/integration-tests/security/src/main/resources/application.properties index e4bb70b7..1f4b8623 100644 --- a/client/integration-tests/security/src/main/resources/application.properties +++ b/client/integration-tests/security/src/main/resources/application.properties @@ -32,12 +32,17 @@ quarkus.openapi-generator.codegen.spec.token_propagation_external_service2_yaml. quarkus.openapi-generator.codegen.spec.token_propagation_external_service3_yaml.base-package=org.acme.externalservice3 quarkus.openapi-generator.codegen.spec.token_propagation_external_service4_yaml.base-package=org.acme.externalservice4 quarkus.openapi-generator.codegen.spec.token_propagation_external_service5_yaml.base-package=org.acme.externalservice5 +quarkus.openapi-generator.codegen.spec.token_propagation_external_service6_with_base_url_yaml.base-package=org.acme.externalservice6 +quarkus.openapi-generator.codegen.spec.token_propagation_external_service7_with_base_url_yaml.base-package=org.acme.externalservice7 quarkus.rest-client.token_propagation_external_service1_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service2_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service3_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service4_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service5_yaml.url=${propagation-external-service-mock.url} +# service6 and service7 endpoints has the base url /external/api/v1 +quarkus.rest-client.token_propagation_external_service6_with_base_url_yaml.url=${propagation-external-service-mock.url}/external/api/v1 +quarkus.rest-client.token_propagation_external_service7_with_base_url_yaml.url=${propagation-external-service-mock.url}/external/api/v1 # default propagation for token_propagation_external_service1 invocation quarkus.openapi-generator.token_propagation_external_service1_yaml.auth.service1_http_bearer.token-propagation=true @@ -50,6 +55,13 @@ quarkus.openapi-generator.token_propagation_external_service3_yaml.auth.service3 quarkus.openapi-generator.token_propagation_external_service4_yaml.auth.service4_oauth2.token-propagation=true quarkus.openapi-generator.token_propagation_external_service4_yaml.auth.service4_oauth2.header-name=SERVICE4_HEADER_TO_PROPAGATE +# default propagation for token_propagation_external_service6_with_base_url invocation +quarkus.openapi-generator.token_propagation_external_service6_with_base_url_yaml.auth.service6_http_bearer.token-propagation=true + +# propagate the token coming in the header SERVICE7_HEADER_TO_PROPAGATE for token_propagation_external_service7_with_base_url invocation +quarkus.openapi-generator.token_propagation_external_service7_with_base_url_yaml.auth.service7_http_bearer.token-propagation=true +quarkus.openapi-generator.token_propagation_external_service7_with_base_url_yaml.auth.service7_http_bearer.header-name=SERVICE7_HEADER_TO_PROPAGATE + # Oidc clients for the services that has oauth2 security. # Oidc client used by the token_propagation_external_service2 quarkus.oidc-client.service2_oauth2.auth-server-url=${keycloak.mock.service.url} diff --git a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java index 53ecbf2a..d30e927b 100644 --- a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java +++ b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java @@ -29,6 +29,9 @@ public class TokenPropagationExternalServicesMock implements QuarkusTestResource public static final String SERVICE3_AUTHORIZATION_TOKEN = "SERVICE3_AUTHORIZATION_TOKEN"; public static final String SERVICE4_HEADER_TO_PROPAGATE = "SERVICE4_HEADER_TO_PROPAGATE"; public static final String SERVICE4_AUTHORIZATION_TOKEN = "SERVICE4_AUTHORIZATION_TOKEN"; + public static final String SERVICE6_AUTHORIZATION_TOKEN = "SERVICE6_AUTHORIZATION_TOKEN"; + public static final String SERVICE7_HEADER_TO_PROPAGATE = "SERVICE7_HEADER_TO_PROPAGATE"; + public static final String SERVICE7_AUTHORIZATION_TOKEN = "SERVICE7_AUTHORIZATION_TOKEN"; public static final String TOKEN_PROPAGATION_EXTERNAL_SERVICE_MOCK_URL = "propagation-external-service-mock.url"; private static final String BEARER = "Bearer "; private static final Logger LOGGER = LoggerFactory.getLogger(TokenPropagationExternalServicesMock.class); @@ -66,6 +69,14 @@ public Map start() { // configured. stubForExternalService("/token-propagation-external-service5/executeQuery5", KEYCLOAK_ACCESS_TOKEN); + // stub the token-propagation-external-service6-with-base-url invocation with the expected token, emulate also the endpoint base url /external/api/v1 + stubForExternalService("/external/api/v1/token-propagation-external-service6-with-base-url/executeQuery6", + SERVICE6_AUTHORIZATION_TOKEN); + + // stub the token-propagation-external-service7-with-base-url invocation with the expected token, emulate also the endpoint base url /external/api/v1 + stubForExternalService("/external/api/v1/token-propagation-external-service7-with-base-url/executeQuery7", + SERVICE7_AUTHORIZATION_TOKEN); + return Map.of(TOKEN_PROPAGATION_EXTERNAL_SERVICE_MOCK_URL, wireMockServer.baseUrl()); } diff --git a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java index a131c53e..5f887e36 100644 --- a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java +++ b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java @@ -6,6 +6,9 @@ import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE3_HEADER_TO_PROPAGATE; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_AUTHORIZATION_TOKEN; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_HEADER_TO_PROPAGATE; +import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE6_AUTHORIZATION_TOKEN; +import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE7_AUTHORIZATION_TOKEN; +import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE7_HEADER_TO_PROPAGATE; import static io.restassured.RestAssured.given; import java.util.Collections; @@ -63,8 +66,10 @@ private static Stream serviceInvocationParams() { // service token-propagation-external-service3 will receive the SERVICE3_AUTHORIZATION_TOKEN Arguments.of("service3", new HeaderArgument(SERVICE3_HEADER_TO_PROPAGATE, SERVICE3_AUTHORIZATION_TOKEN)), // service token-propagation-external-service4 will receive the SERVICE4_AUTHORIZATION_TOKEN - Arguments.of("service4", new HeaderArgument(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN)) - - ); + Arguments.of("service4", new HeaderArgument(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN)), + // service token-propagation-external-service6-with-base-url will receive the SERVICE6_AUTHORIZATION_TOKEN + Arguments.of("service6", new HeaderArgument(HttpHeaders.AUTHORIZATION, SERVICE6_AUTHORIZATION_TOKEN)), + // service token-propagation-external-service7-with-base-url will receive the SERVICE7_AUTHORIZATION_TOKEN + Arguments.of("service7", new HeaderArgument(SERVICE7_HEADER_TO_PROPAGATE, SERVICE7_AUTHORIZATION_TOKEN))); } } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BaseCompositeAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BaseCompositeAuthenticationProvider.java index a6d705f4..26aae53e 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BaseCompositeAuthenticationProvider.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BaseCompositeAuthenticationProvider.java @@ -3,6 +3,7 @@ import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderNamePrefix; import java.io.IOException; +import java.net.URI; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -10,6 +11,8 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; +import org.eclipse.microprofile.config.ConfigProvider; + import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig; /** @@ -47,9 +50,53 @@ public void filter(ClientRequestContext requestContext) throws IOException { * It can perform the authentication filter only if this operation requires it (has a security reference) */ private boolean canFilter(final AuthProvider authProvider, final ClientRequestContext requestContext) { + String sanitizedPath = sanitizePath(authProvider, requestContext.getUri()); return authProvider.operationsToFilter().stream() .anyMatch(o -> o.getHttpMethod().equals(requestContext.getMethod()) && - o.matchPath(requestContext.getUri().getPath())); + o.matchPath(sanitizedPath)); + } + + /** + * Calculates the path to realize the matching with the OpenAPI operations, considering that the Quarkus client is + * configured with a URI that refers to the OpenAPI service endpoint. e.g.: + * + * In the OpenAPI document below the service endpoint is: http://https://development.gigantic-server.com/v1, + * + * openapi: 3.0.3 + * servers: + * - url: https://development.gigantic-server.com/v1 + * description: Development server + * ... + * paths: + * /some-operation: + * post: + * operationId: SomeOperation + * ... + * + * And thus, the Quarkus client must be configured like this: + * quarkus.rest-client.example-auth_yaml.url=https://development.gigantic-server.com/v1 + * + * @param authProvider authentication provider to realize the matching with. + * @param requestUri the request url to invoke, e.g. https://development.gigantic-server.com/v1/some-operation + * @return The sanitized path to realize the matching with the OpenApi operations path, following the example above + * must be: /some-operation + */ + protected String sanitizePath(AuthProvider authProvider, URI requestUri) { + String basePath = getRestClientURLConfig(authProvider).getPath(); + basePath = basePath.endsWith("/") ? basePath.substring(0, basePath.length() - 1) : basePath; + String requestPath = requestUri.getPath(); + if (!basePath.isEmpty() && requestPath.startsWith(basePath)) { + return requestPath.substring(basePath.length()); + } + return requestPath; + } + + protected URI getRestClientURLConfig(AuthProvider authProvider) { + String openApiSpecId = ((AbstractAuthProvider) authProvider).getOpenApiSpecId(); + String restClientProperty = String.format("quarkus.rest-client.%s.url", openApiSpecId); + return ConfigProvider.getConfig().getOptionalValue(restClientProperty, URI.class) + .orElseThrow(() -> new IllegalArgumentException( + "Required rest client property is not configured: " + restClientProperty)); } protected static String sanitizeAuthName(String schemeName) {