From 5e9c009b53f585ed15657a78f5f0dae4548640ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 16:42:13 +0200 Subject: [PATCH 01/12] Enable admin authentication on API gateway --- .../pom.xml | 30 ++++ .../HttpAdminAuthenticationProvider.java | 81 ++++++++++ ...inAuthenticationProviderConfiguration.java | 39 +++++ ...gateway.GatewayAdminAuthenticationProvider | 1 + .../langstream-jwt-gateway-admin-auth/pom.xml | 26 ++++ .../admin/JwtAdminAuthenticationProvider.java | 63 ++++++++ ...inAuthenticationProviderConfiguration.java | 32 ++++ ...gateway.GatewayAdminAuthenticationProvider | 1 + langstream-api-gateway-auth/pom.xml | 2 + langstream-api-gateway/pom.xml | 19 +++ .../apigateway/LangStreamApiGateway.java | 3 +- .../GatewayAdminAuthenticationProperties.java | 37 +++++ .../websocket/AuthenticationInterceptor.java | 99 +++++++++--- .../apigateway/websocket/WebSocketConfig.java | 5 +- .../websocket/handlers/AbstractHandler.java | 100 ++++++------ .../impl/GatewayRequestContextImpl.java | 84 ++++++++++ .../handlers/ProduceConsumeHandlerTest.java | 143 +++++++++++++++++- .../GatewayAdminAuthenticationProvider.java | 27 ++++ .../gateway/GatewayAdminRequestContext.java | 27 ++++ ...GatewayAuthenticationProviderRegistry.java | 22 +++ .../java/ai/langstream/api/model/Gateway.java | 6 +- langstream-auth-jwt/pom.xml | 54 +++++++ .../jwt}/AuthenticationProviderToken.java | 15 +- .../auth/jwt}/JwksUriSigningKeyResolver.java | 2 +- .../ai/langstream/auth/jwt/JwtProperties.java | 12 ++ ...alKubernetesJwksUriSigningKeyResolver.java | 2 +- .../ApplicationPlaceholderResolver.java | 3 +- langstream-webservice/pom.xml | 18 +-- .../primary/TokenAuthFilter.java | 18 ++- pom.xml | 1 + 30 files changed, 868 insertions(+), 104 deletions(-) create mode 100644 langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml create mode 100644 langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java create mode 100644 langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java create mode 100644 langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider create mode 100644 langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml create mode 100644 langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java create mode 100644 langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java create mode 100644 langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider create mode 100644 langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java create mode 100644 langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java create mode 100644 langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java create mode 100644 langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java create mode 100644 langstream-auth-jwt/pom.xml rename {langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary => langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt}/AuthenticationProviderToken.java (94%) rename {langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary => langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt}/JwksUriSigningKeyResolver.java (99%) create mode 100644 langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java rename {langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary => langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt}/LocalKubernetesJwksUriSigningKeyResolver.java (99%) diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml new file mode 100644 index 000000000..8209efae2 --- /dev/null +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml @@ -0,0 +1,30 @@ + + + + langstream-api-gateway-auth + ai.langstream + 0.0.14-SNAPSHOT + + 4.0.0 + + langstream-http-gateway-admin-auth + + + + ai.langstream + langstream-api + ${project.version} + + + org.slf4j + slf4j-api + provided + + + com.fasterxml.jackson.core + jackson-databind + + + \ No newline at end of file diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java new file mode 100644 index 000000000..137212b32 --- /dev/null +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java @@ -0,0 +1,81 @@ +package ai.langstream.apigateway.auth.impl.jwt.admin; + +import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; +import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.gateway.GatewayAuthenticationResult; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Map; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HttpAdminAuthenticationProvider implements GatewayAdminAuthenticationProvider { + + private static final ObjectMapper mapper = new ObjectMapper(); + private HttpAdminAuthenticationProviderConfiguration httpConfiguration; + private HttpClient httpClient; + + + @Override + public String type() { + return "http"; + } + + @Override + @SneakyThrows + public void initialize(Map configuration) { + httpConfiguration = + mapper.convertValue(configuration, HttpAdminAuthenticationProviderConfiguration.class); + httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(30)) + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + } + + @Override + public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context) { + + final Map placeholders = Map.of("tenant", context.tenant()); + final String uri = resolvePlaceholders(placeholders, httpConfiguration.getPathTemplate()); + final String url = httpConfiguration.getBaseUrl() + uri; + + log.info("Authenticating admin with url: {}", url); + + + final HttpRequest.Builder builder = HttpRequest.newBuilder() + .uri(URI.create(url)); + + httpConfiguration.getHeaders().forEach(builder::header); + builder.header("Authorization", "Bearer " + context.credentials()); + final HttpRequest request = builder + .GET() + .build(); + + final HttpResponse response; + try { + response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (Throwable e) { + return GatewayAuthenticationResult.authenticationFailed(e.getMessage()); + } + if (httpConfiguration.getAcceptedStatuses().contains(response.statusCode())) { + return GatewayAuthenticationResult.authenticationSuccessful( + context.adminCredentialsInputs()); + } + return GatewayAuthenticationResult.authenticationFailed("Http authentication failed: " + response.statusCode()); + } + + private static String resolvePlaceholders(Map placeholders, String url) { + for (Map.Entry entry : placeholders.entrySet()) { + url = url.replace("{" + entry.getKey() + "}", entry.getValue()); + } + return url; + } +} diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java new file mode 100644 index 000000000..4b0ba8d3f --- /dev/null +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.apigateway.auth.impl.jwt.admin; + +import com.fasterxml.jackson.annotation.JsonAlias; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class HttpAdminAuthenticationProviderConfiguration { + + @JsonAlias("base-url") + private String baseUrl; + @JsonAlias("path-template") + private String pathTemplate; + private Map headers = new HashMap<>(); + @JsonAlias("accepted-statuses") + private List acceptedStatuses = List.of(200, 201); + +} diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider new file mode 100644 index 000000000..f471bc6af --- /dev/null +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider @@ -0,0 +1 @@ +ai.langstream.apigateway.auth.impl.jwt.admin.HttpAdminAuthenticationProvider \ No newline at end of file diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml new file mode 100644 index 000000000..0c9be83f0 --- /dev/null +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml @@ -0,0 +1,26 @@ + + + + langstream-api-gateway-auth + ai.langstream + 0.0.14-SNAPSHOT + + 4.0.0 + + langstream-jwt-gateway-admin-auth + + + + ai.langstream + langstream-api + ${project.version} + + + ai.langstream + langstream-auth-jwt + ${project.version} + + + \ No newline at end of file diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java new file mode 100644 index 000000000..382c557c5 --- /dev/null +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java @@ -0,0 +1,63 @@ +package ai.langstream.apigateway.auth.impl.jwt.admin; + +import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; +import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.gateway.GatewayAuthenticationResult; +import ai.langstream.auth.jwt.AuthenticationProviderToken; +import ai.langstream.auth.jwt.JwtProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.Map; +import lombok.SneakyThrows; + +public class JwtAdminAuthenticationProvider implements GatewayAdminAuthenticationProvider { + + private static final ObjectMapper mapper = new ObjectMapper(); + private AuthenticationProviderToken authenticationProviderToken; + private List adminRoles; + + @Override + public String type() { + return "jwt"; + } + + @Override + @SneakyThrows + public void initialize(Map configuration) { + final JwtAdminAuthenticationProviderConfiguration tokenProperties = + mapper.convertValue(configuration, JwtAdminAuthenticationProviderConfiguration.class); + + + if (tokenProperties.adminRoles() != null) { + this.adminRoles = tokenProperties.adminRoles(); + } else { + this.adminRoles = List.of(); + } + + + final JwtProperties jwtProperties = new JwtProperties( + tokenProperties.secretKey(), + tokenProperties.publicKey(), + tokenProperties.authClaim(), + tokenProperties.publicAlg(), + tokenProperties.audienceClaim(), + tokenProperties.audience(), + tokenProperties.jwksHostsAllowlist()); + this.authenticationProviderToken = new AuthenticationProviderToken(jwtProperties); + } + + @Override + public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context) { + String role; + try { + role = authenticationProviderToken.authenticate(context.credentials()); + } catch (AuthenticationProviderToken.AuthenticationException ex) { + return GatewayAuthenticationResult.authenticationFailed(ex.getMessage()); + } + if (!adminRoles.contains(role)) { + return GatewayAuthenticationResult.authenticationFailed("Not an admin."); + } + return GatewayAuthenticationResult.authenticationSuccessful( + context.adminCredentialsInputs()); + } +} diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java new file mode 100644 index 000000000..71d8498bd --- /dev/null +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.apigateway.auth.impl.jwt.admin; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +public record JwtAdminAuthenticationProviderConfiguration( + String secretKey, + String publicKey, + String authClaim, + String publicAlg, + String audienceClaim, + String audience, + List adminRoles, + String jwksHostsAllowlist) { +} diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider new file mode 100644 index 000000000..2606bcbd5 --- /dev/null +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider @@ -0,0 +1 @@ +ai.langstream.apigateway.auth.impl.jwt.admin.JwtAdminAuthenticationProvider \ No newline at end of file diff --git a/langstream-api-gateway-auth/pom.xml b/langstream-api-gateway-auth/pom.xml index 67b135777..6f3c22932 100644 --- a/langstream-api-gateway-auth/pom.xml +++ b/langstream-api-gateway-auth/pom.xml @@ -30,5 +30,7 @@ langstream-google-api-gateway-auth langstream-github-api-gateway-auth + langstream-jwt-gateway-admin-auth + langstream-http-gateway-admin-auth diff --git a/langstream-api-gateway/pom.xml b/langstream-api-gateway/pom.xml index d970b7b39..4853cc311 100644 --- a/langstream-api-gateway/pom.xml +++ b/langstream-api-gateway/pom.xml @@ -104,6 +104,11 @@ kafka test + + com.github.tomakehurst + wiremock + test + ai.langstream langstream-core @@ -124,6 +129,20 @@ runtime + + ai.langstream + langstream-jwt-gateway-admin-auth + ${project.version} + runtime + + + + ai.langstream + langstream-http-gateway-admin-auth + ${project.version} + runtime + + diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java index d8ba089cc..42abb33cb 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java @@ -15,6 +15,7 @@ */ package ai.langstream.apigateway; +import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; import ai.langstream.apigateway.config.StorageProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +25,7 @@ import org.springframework.core.env.Environment; @SpringBootApplication -@EnableConfigurationProperties({StorageProperties.class}) +@EnableConfigurationProperties({StorageProperties.class, GatewayAdminAuthenticationProperties.class}) public class LangStreamApiGateway { static { diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java new file mode 100644 index 000000000..f23b66b8e --- /dev/null +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.apigateway.config; + +import com.fasterxml.jackson.annotation.JsonAlias; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "application.gateways.auth.admin") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GatewayAdminAuthenticationProperties { + + private List types; + @JsonAlias("default-type") + private String defaultType; + private Map> configuration = new HashMap<>(); +} diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java index 9c86d3c9f..d1f01f6ae 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java @@ -15,16 +15,20 @@ */ package ai.langstream.apigateway.websocket; +import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationProviderRegistry; import ai.langstream.api.gateway.GatewayAuthenticationResult; import ai.langstream.api.gateway.GatewayRequestContext; import ai.langstream.api.model.Application; import ai.langstream.api.model.Gateway; +import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; import ai.langstream.apigateway.websocket.handlers.AbstractHandler; +import ai.langstream.apigateway.websocket.impl.GatewayRequestContextImpl; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -40,6 +44,34 @@ @Slf4j public class AuthenticationInterceptor implements HandshakeInterceptor { + private final Map adminAuthProviders; + private final String defaultProvider; + + + public AuthenticationInterceptor( + GatewayAdminAuthenticationProperties adminAuthenticationProperties) { + final List types = adminAuthenticationProperties.getTypes(); + Map providers = new HashMap<>(); + String defaultProvider = null; + if (types != null) { + for (String type : types) { + // preload all admin providers to avoid concurrency, add cache and check configuration sanity + final GatewayAdminAuthenticationProvider provider = + GatewayAuthenticationProviderRegistry.loadAdminProvider( + type, adminAuthenticationProperties.getConfiguration().get(type)); + providers.put(type, provider); + if (adminAuthenticationProperties.getDefaultType() != null && adminAuthenticationProperties.getDefaultType().equals(type)) { + defaultProvider = type; + } + } + if (types.size() == 1) { + defaultProvider = types.get(0); + } + } + this.adminAuthProviders = providers; + this.defaultProvider = defaultProvider; + } + @Override public boolean beforeHandshake( ServerHttpRequest request, @@ -61,7 +93,7 @@ public boolean beforeHandshake( final String path = httpRequest.getURI().getPath(); final Map vars = antPathMatcher.extractUriTemplateVariables(handler.path(), path); - final GatewayRequestContext gatewayRequestContext = + final GatewayRequestContextImpl gatewayRequestContext = handler.validateRequest( vars, querystring, request.getHeaders().toSingleValueMap()); @@ -100,30 +132,61 @@ public AuthFailedException(String message) { } } - private Map authenticate(GatewayRequestContext gatewayRequestContext) + private Map authenticate(GatewayRequestContextImpl gatewayRequestContext) throws AuthFailedException { + final Gateway.Authentication authentication = gatewayRequestContext.gateway().authentication(); - final Map principalValues; - if (authentication != null) { - final String provider = authentication.provider(); - - final GatewayAuthenticationProvider authenticationProvider = - GatewayAuthenticationProviderRegistry.loadProvider( - provider, authentication.configuration()); - final GatewayAuthenticationResult result = - authenticationProvider.authenticate(gatewayRequestContext); - if (!result.authenticated()) { - throw new AuthFailedException(result.reason()); - } - principalValues = result.principalValues(); + + if (authentication == null) { + return Map.of(); + } + + final GatewayAuthenticationResult result; + if (gatewayRequestContext.isAdminRequest()) { + if (!authentication.allowAdminRequests()) { + throw new AuthFailedException("Gateway " + gatewayRequestContext.gateway().id() + + " of tenant " + gatewayRequestContext.tenant() + " does not allow admin requests."); + } + String provider = gatewayRequestContext.adminCredentialsType(); + if (provider == null && defaultProvider != null) { + provider = defaultProvider; + } + if (provider == null) { + throw new AuthFailedException("No admin auth provider specified"); + } + + final GatewayAdminAuthenticationProvider gatewayAdminAuthenticationProvider = + adminAuthProviders.get(provider); + if (gatewayAdminAuthenticationProvider == null) { + throw new AuthFailedException("Unknown admin auth provider " + provider); + } + result = gatewayAdminAuthenticationProvider.authenticate(gatewayRequestContext); } else { - principalValues = Map.of(); + + if (authentication != null) { + final String provider = authentication.provider(); + + final GatewayAuthenticationProvider authenticationProvider = + GatewayAuthenticationProviderRegistry.loadProvider( + provider, authentication.configuration()); + result = + authenticationProvider.authenticate(gatewayRequestContext); + } else { + result = null; + } + } + if (result == null) { + return Map.of(); + } + if (!result.authenticated()) { + throw new AuthFailedException(result.reason()); } - if (principalValues == null) { + final Map values = result.principalValues(); + if (values == null) { return Map.of(); } - return principalValues; + return values; } private AuthenticatedGatewayRequestContext getAuthenticatedGatewayRequestContext( diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java index 1d41a1548..ae876c749 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java @@ -16,6 +16,7 @@ package ai.langstream.apigateway.websocket; import ai.langstream.api.storage.ApplicationStore; +import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; import ai.langstream.apigateway.websocket.handlers.ConsumeHandler; import ai.langstream.apigateway.websocket.handlers.ProduceHandler; import jakarta.annotation.PreDestroy; @@ -41,6 +42,7 @@ public class WebSocketConfig implements WebSocketConfigurer { public static final String PRODUCE_PATH = "/v1/produce/{tenant}/{application}/{gateway}"; private final ApplicationStore applicationStore; + private final GatewayAdminAuthenticationProperties adminAuthenticationProperties; private final ExecutorService consumeThreadPool = Executors.newCachedThreadPool(); @Override @@ -49,7 +51,8 @@ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { .addHandler(new ProduceHandler(applicationStore), PRODUCE_PATH) .setAllowedOrigins("*") .addInterceptors( - new HttpSessionHandshakeInterceptor(), new AuthenticationInterceptor()); + new HttpSessionHandshakeInterceptor(), + new AuthenticationInterceptor(adminAuthenticationProperties)); } @Bean diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java index ddcc88629..89dcbc36a 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java @@ -30,6 +30,7 @@ import ai.langstream.api.runner.topics.TopicProducer; import ai.langstream.api.storage.ApplicationStore; import ai.langstream.apigateway.websocket.AuthenticatedGatewayRequestContext; +import ai.langstream.apigateway.websocket.impl.GatewayRequestContextImpl; import ai.langstream.impl.common.ApplicationPlaceholderResolver; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; @@ -72,7 +73,8 @@ abstract String gatewayFromPath( public void onBeforeHandshakeCompleted( AuthenticatedGatewayRequestContext gatewayRequestContext, Map attributes) - throws Exception {} + throws Exception { + } abstract void onOpen( WebSocketSession webSocketSession, @@ -181,15 +183,15 @@ private Gateway extractGateway( if (selectedGateway == null) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " is not defined in the application"); + + gatewayId + + " of type " + + type + + " is not defined in the application"); } return selectedGateway; } - public GatewayRequestContext validateRequest( + public GatewayRequestContextImpl validateRequest( Map pathVars, Map queryString, Map httpHeaders) { @@ -197,8 +199,19 @@ public GatewayRequestContext validateRequest( Map userParameters = new HashMap<>(); final String credentials = queryString.remove("credentials"); + final String adminCredentials = queryString.remove("admin-credentials"); + final String adminCredentialsType = queryString.remove("admin-credentials-type"); + + + final Map adminCredentialsInputs = new HashMap<>(); for (Map.Entry entry : queryString.entrySet()) { + if (entry.getKey().startsWith("admin-credentials-input-")) { + adminCredentialsInputs.put( + entry.getKey().substring("admin-credentials-input-".length()), + entry.getValue()); + continue; + } if (entry.getKey().startsWith("option:")) { options.put(entry.getKey().substring("option:".length()), entry.getValue()); } else if (entry.getKey().startsWith("param:")) { @@ -206,10 +219,10 @@ public GatewayRequestContext validateRequest( } else { throw new IllegalArgumentException( "invalid query parameter " - + entry.getKey() - + ". " - + "To specify a gateway parameter, use the format param:." - + "To specify a option, use the format option:."); + + entry.getKey() + + ". " + + "To specify a gateway parameter, use the format param:." + + "To specify a option, use the format option:."); } } @@ -238,48 +251,25 @@ public GatewayRequestContext validateRequest( } validateOptions(options); - return new GatewayRequestContext() { - - @Override - public String tenant() { - return tenant; - } - - @Override - public String applicationId() { - return applicationId; - } - - @Override - public Application application() { - return application; - } - - @Override - public Gateway gateway() { - return gateway; - } - - @Override - public String credentials() { - return credentials; - } - - @Override - public Map userParameters() { - return userParameters; - } - - @Override - public Map options() { - return options; - } + if (credentials != null && ( + !adminCredentialsInputs.isEmpty() || adminCredentials != null || adminCredentialsType != null)) { + throw new IllegalArgumentException( + "credentials and admin-credentials cannot be used together"); + } + return GatewayRequestContextImpl.builder() + .tenant(tenant) + .applicationId(applicationId) + .application(application) + .credentials(credentials) + .adminCredentialsType(adminCredentialsType) + .adminCredentials(adminCredentials) + .adminCredentialsInputs(adminCredentialsInputs) + .httpHeaders(httpHeaders) + .options(options) + .userParameters(userParameters) + .gateway(gateway) + .build(); - @Override - public Map httpHeaders() { - return httpHeaders; - } - }; } protected void recordCloseableResource( @@ -332,10 +322,10 @@ protected void sendEvent(EventRecord.Types type, AuthenticatedGatewayRequestCont TOPIC_CONNECTIONS_REGISTRY.getTopicConnectionsRuntime(streamingCluster); try (final TopicProducer producer = - topicConnectionsRuntime.createProducer( - "langstream-events", - streamingCluster, - Map.of("topic", gateway.eventsTopic())); ) { + topicConnectionsRuntime.createProducer( + "langstream-events", + streamingCluster, + Map.of("topic", gateway.eventsTopic()));) { producer.start(); final EventSources.GatewaySource source = diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java new file mode 100644 index 000000000..5d981aa23 --- /dev/null +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java @@ -0,0 +1,84 @@ +package ai.langstream.apigateway.websocket.impl; + +import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.model.Application; +import ai.langstream.api.model.Gateway; +import java.util.Map; +import lombok.Builder; + +@Builder +public class GatewayRequestContextImpl implements GatewayAdminRequestContext { + + private final String tenant; + private final String applicationId; + private final Application application; + private final Gateway gateway; + private final String credentials; + private final String adminCredentials; + private final String adminCredentialsType; + private final Map adminCredentialsInputs; + private final Map userParameters; + private final Map options; + private final Map httpHeaders; + + + @Override + public String tenant() { + return tenant; + } + + @Override + public String applicationId() { + return applicationId; + } + + @Override + public Application application() { + return application; + } + + @Override + public Gateway gateway() { + return gateway; + } + + @Override + public String credentials() { + if (isAdminRequest()) { + return adminCredentials; + } + return credentials; + } + + public boolean isAdminRequest() { + return adminCredentials != null; + } + + @Override + public Map userParameters() { + return userParameters; + } + + @Override + public Map options() { + return options; + } + + @Override + public Map httpHeaders() { + return httpHeaders; + } + + @Override + public String adminCredentialsType() { + if (isAdminRequest()) { + return adminCredentialsType; + } + throw new UnsupportedOperationException(); + } + + @Override + public Map adminCredentialsInputs() { + return isAdminRequest() ? adminCredentialsInputs : Map.of(); + } +} diff --git a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java index 47f3f7c5b..ae39bd56b 100644 --- a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java +++ b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import ai.langstream.api.events.EventRecord; @@ -36,6 +37,7 @@ import ai.langstream.api.runtime.ClusterRuntimeRegistry; import ai.langstream.api.runtime.PluginsRegistry; import ai.langstream.api.storage.ApplicationStore; +import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; import ai.langstream.apigateway.websocket.api.ConsumePushMessage; import ai.langstream.apigateway.websocket.api.ProduceRequest; import ai.langstream.apigateway.websocket.api.ProduceResponse; @@ -45,10 +47,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; import jakarta.websocket.CloseReason; import jakarta.websocket.DeploymentException; import jakarta.websocket.Session; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -62,9 +69,11 @@ import org.awaitility.Awaitility; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; @@ -77,7 +86,10 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = {"spring.main.allow-bean-definition-overriding=true"}) + properties = { + "spring.main.allow-bean-definition-overriding=true", + }) +@WireMockTest class ProduceConsumeHandlerTest { protected static final ObjectMapper MAPPER = new ObjectMapper(); @@ -88,6 +100,7 @@ class ProduceConsumeHandlerTest { static List topics; static Gateways testGateways; + @TestConfiguration public static class WebSocketTestConfig { @@ -110,6 +123,21 @@ public ApplicationStore store() { return mock; } + + @Bean + @Primary + public GatewayAdminAuthenticationProperties gatewayAdminAuthenticationProperties() { + final GatewayAdminAuthenticationProperties props = + new GatewayAdminAuthenticationProperties(); + props.setTypes(List.of("http")); + props.setConfiguration(Map.of("http", Map.of( + "base-url", wireMockBaseUrl, + "path-template", "/auth/{tenant}", + "headers", Map.of("h1", "v1") + ))); + return props; + } + } @NotNull @@ -158,8 +186,17 @@ private static Application buildApp() throws Exception { @Autowired ApplicationStore store; + static WireMock wireMock; + static String wireMockBaseUrl; + + @BeforeAll + public static void beforeAll(WireMockRuntimeInfo wmRuntimeInfo) { + wireMock = wmRuntimeInfo.getWireMock(); + wireMockBaseUrl = wmRuntimeInfo.getHttpBaseUrl(); + } + @BeforeEach - public void beforeEach() { + public void beforeEach(WireMockRuntimeInfo wmRuntimeInfo) { testGateways = null; topics = null; Awaitility.setDefaultTimeout(30, TimeUnit.SECONDS); @@ -510,6 +547,108 @@ void testAuthentication() { assertEquals(List.of(), user2Messages); } + + @Test + void testAdminAuthentication() { + wireMock.register( + WireMock.get("/auth/tenant1") + .withHeader("Authorization", WireMock.equalTo("Bearer test-user-password")) + .withHeader("h1", WireMock.equalTo("v1")) + .willReturn(WireMock.ok(""))); + final String topic = genTopic(); + prepareTopicsForTest(topic); + + List user1Messages = new ArrayList<>(); + + testGateways = + new Gateways( + List.of( + new Gateway( + "produce", + Gateway.GatewayType.produce, + topic, + new Gateway.Authentication("test-auth", Map.of(), true), + List.of(), + new Gateway.ProduceOptions( + List.of( + Gateway.KeyValueComparison + .valueFromAuthentication( + "header1", "user-id"))), + null), + new Gateway( + "consume", + Gateway.GatewayType.consume, + topic, + new Gateway.Authentication("test-auth", Map.of(), true), + List.of(), + null, + new Gateway.ConsumeOptions( + new Gateway.ConsumeOptionsFilters( + List.of( + Gateway.KeyValueComparison + .valueFromAuthentication( + "header1", + "user-id"))))), + new Gateway( + "consume-no-admin", + Gateway.GatewayType.consume, + topic, + new Gateway.Authentication("test-auth", Map.of(), false), + List.of(), + null, + null))); + + @Cleanup + final ClientSession client1 = + connectAndCollectMessages( + URI.create( + "ws://localhost:%d/v1/consume/tenant1/application1/consume?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + .formatted(port)), + user1Messages); + + + + connectAndProduce( + URI.create( + "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + .formatted(port)), + new ProduceRequest(null, "hello user", null)); + + + Awaitility.await() + .untilAsserted( + () -> + assertMessagesContent( + List.of( + new MsgRecord( + null, + "hello user", + Map.of("header1", "mock-user"))), + user1Messages)); + + connectAndExpectClose( + URI.create( + "ws://localhost:%d/v1/consume/tenant1/application1/consume-no-admin?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + .formatted(port)), new CloseReason( + CloseReason.CloseCodes.VIOLATED_POLICY, + "Gateway consume-no-admin of tenant tenant1 does not allow admin requests.")); + + // use default admin auth provider + connectAndProduce( + URI.create( + "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password&admin-credentials-input-user-id=mock-user" + .formatted(port)), + new ProduceRequest(null, "hello user", null)); + + connectAndExpectClose( + URI.create( + "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password-but-wrong&admin-credentials-input-user-id=mock-user" + .formatted(port)), new CloseReason( + CloseReason.CloseCodes.VIOLATED_POLICY, + "Gateway produce of tenant tenant1 does not allow admin requests.")); + + } + private record MsgRecord(Object key, Object value, Map headers) {} private void assertMessagesContent(List expected, List actual) { diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java new file mode 100644 index 000000000..6432d470e --- /dev/null +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.api.gateway; + +import java.util.Map; + +public interface GatewayAdminAuthenticationProvider { + + String type(); + + void initialize(Map configuration); + + GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context); +} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java new file mode 100644 index 000000000..77ec2fcb2 --- /dev/null +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java @@ -0,0 +1,27 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.api.gateway; + +import ai.langstream.api.model.Application; +import ai.langstream.api.model.Gateway; +import java.util.Map; + +public interface GatewayAdminRequestContext extends GatewayRequestContext { + + String adminCredentialsType(); + + Map adminCredentialsInputs(); +} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java index f34784f89..cde68e541 100644 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java @@ -42,4 +42,26 @@ public static GatewayAuthenticationProvider loadProvider( store.initialize(configuration); return store; } + + public static GatewayAdminAuthenticationProvider loadAdminProvider( + String type, Map configuration) { + Objects.requireNonNull(type, "type cannot be null"); + if (configuration == null) { + configuration = Map.of(); + } + ServiceLoader loader = + ServiceLoader.load(GatewayAdminAuthenticationProvider.class); + final GatewayAdminAuthenticationProvider store = + loader.stream() + .filter(p -> type.equals(p.get().type())) + .findFirst() + .orElseThrow( + () -> + new RuntimeException( + "No GatewayAdminAuthenticationProvider found for type " + + type)) + .get(); + store.initialize(configuration); + return store; + } } diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index 5a9ec9b17..93b32cff3 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -55,7 +55,11 @@ public Gateway( this(id, type, topic, authentication, parameters, produceOptions, consumeOptions, null); } - public record Authentication(String provider, Map configuration) {} + public record Authentication(String provider, Map configuration, boolean allowAdminRequests) { + public Authentication(String provider, Map configuration) { + this(provider, configuration, false); + } + } public record KeyValueComparison( String key, String value, String valueFromParameters, String valueFromAuthentication) { diff --git a/langstream-auth-jwt/pom.xml b/langstream-auth-jwt/pom.xml new file mode 100644 index 000000000..54ca94115 --- /dev/null +++ b/langstream-auth-jwt/pom.xml @@ -0,0 +1,54 @@ + + + + langstream-ai + ai.langstream + 0.0.14-SNAPSHOT + + 4.0.0 + + langstream-auth-jwt + + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + provided + + + org.apache.commons + commons-lang3 + + + commons-codec + commons-codec + + + commons-io + commons-io + + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + + + io.jsonwebtoken + jjwt-jackson + + + + + + + \ No newline at end of file diff --git a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/AuthenticationProviderToken.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java similarity index 94% rename from langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/AuthenticationProviderToken.java rename to langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java index d08d8b3bb..8252639a8 100644 --- a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/AuthenticationProviderToken.java +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ai.langstream.webservice.security.infrastructure.primary; +package ai.langstream.auth.jwt; -import ai.langstream.webservice.config.AuthTokenProperties; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtException; @@ -59,7 +58,7 @@ public AuthenticationException(String message) { private final String audienceClaim; private final String audience; - public AuthenticationProviderToken(AuthTokenProperties tokenProperties) + public AuthenticationProviderToken(JwtProperties tokenProperties) throws IOException, IllegalArgumentException { this.publicKeyAlg = getPublicKeyAlgType(tokenProperties); parser = @@ -151,7 +150,7 @@ private String getPrincipal(Jwt jwt) { } } - private Key getValidationKeyFromConfig(AuthTokenProperties tokenProperties) throws IOException { + private Key getValidationKeyFromConfig(JwtProperties tokenProperties) throws IOException { String tokenSecretKey = tokenProperties.secretKey(); String tokenPublicKey = tokenProperties.publicKey(); byte[] validationKey; @@ -196,12 +195,12 @@ private static SecretKey decodeSecretKey(byte[] secretKey) { return Keys.hmacShaKeyFor(secretKey); } - private String getTokenRoleClaim(AuthTokenProperties tokenProperties) { + private String getTokenRoleClaim(JwtProperties tokenProperties) { String tokenAuthClaim = tokenProperties.authClaim(); return StringUtils.isNotBlank(tokenAuthClaim) ? tokenAuthClaim : "sub"; } - private SignatureAlgorithm getPublicKeyAlgType(AuthTokenProperties tokenProperties) + private SignatureAlgorithm getPublicKeyAlgType(JwtProperties tokenProperties) throws IllegalArgumentException { String tokenPublicAlg = tokenProperties.publicAlg(); if (StringUtils.isNotBlank(tokenPublicAlg)) { @@ -238,13 +237,13 @@ private static String keyTypeForSignatureAlgorithm(SignatureAlgorithm alg) { } } - private String getTokenAudienceClaim(AuthTokenProperties tokenProperties) + private String getTokenAudienceClaim(JwtProperties tokenProperties) throws IllegalArgumentException { String tokenAudienceClaim = tokenProperties.audienceClaim(); return StringUtils.isNotBlank(tokenAudienceClaim) ? tokenAudienceClaim : null; } - private String getTokenAudience(AuthTokenProperties tokenProperties) + private String getTokenAudience(JwtProperties tokenProperties) throws IllegalArgumentException { String tokenAudience = tokenProperties.audience(); return StringUtils.isNotBlank(tokenAudience) ? tokenAudience : null; diff --git a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/JwksUriSigningKeyResolver.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwksUriSigningKeyResolver.java similarity index 99% rename from langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/JwksUriSigningKeyResolver.java rename to langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwksUriSigningKeyResolver.java index 3e06d5eb2..051be17ad 100644 --- a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/JwksUriSigningKeyResolver.java +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwksUriSigningKeyResolver.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ai.langstream.webservice.security.infrastructure.primary; +package ai.langstream.auth.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Claims; diff --git a/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java new file mode 100644 index 000000000..6620d335b --- /dev/null +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java @@ -0,0 +1,12 @@ +package ai.langstream.auth.jwt; + +import java.util.List; + +public record JwtProperties( + String secretKey, + String publicKey, + String authClaim, + String publicAlg, + String audienceClaim, + String audience, + String jwksHostsAllowlist) {} diff --git a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/LocalKubernetesJwksUriSigningKeyResolver.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/LocalKubernetesJwksUriSigningKeyResolver.java similarity index 99% rename from langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/LocalKubernetesJwksUriSigningKeyResolver.java rename to langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/LocalKubernetesJwksUriSigningKeyResolver.java index 5b226a71f..03067d35e 100644 --- a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/LocalKubernetesJwksUriSigningKeyResolver.java +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/LocalKubernetesJwksUriSigningKeyResolver.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ai.langstream.webservice.security.infrastructure.primary; +package ai.langstream.auth.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; diff --git a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java index f83673a32..0a41b0403 100644 --- a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java +++ b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java @@ -181,7 +181,8 @@ private static Gateways resolveGateways(Application instance, Map + + ${project.groupId} + langstream-auth-jwt + ${project.version} + ${project.groupId} langstream-core @@ -152,18 +157,7 @@ zip4j - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - - - io.jsonwebtoken - jjwt-jackson - + commons-codec diff --git a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java b/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java index fe57b52d4..14371d28b 100644 --- a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java +++ b/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java @@ -15,6 +15,8 @@ */ package ai.langstream.webservice.security.infrastructure.primary; +import ai.langstream.auth.jwt.AuthenticationProviderToken; +import ai.langstream.auth.jwt.JwtProperties; import ai.langstream.webservice.config.AuthTokenProperties; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -45,7 +47,17 @@ public class TokenAuthFilter extends GenericFilterBean { @SneakyThrows public TokenAuthFilter(AuthTokenProperties tokenProperties) { this.tokenProperties = tokenProperties; - this.authenticationProvider = new AuthenticationProviderToken(tokenProperties); + + final JwtProperties jwtProperties = new JwtProperties( + tokenProperties.secretKey(), + tokenProperties.publicKey(), + tokenProperties.authClaim(), + tokenProperties.publicAlg(), + tokenProperties.audienceClaim(), + tokenProperties.audience(), + tokenProperties.jwksHostsAllowlist()); + + this.authenticationProvider = new AuthenticationProviderToken(jwtProperties); } @Override @@ -57,7 +69,7 @@ public void doFilter( ((HttpServletRequest) servletRequest).getHeader(HttpHeaders.AUTHORIZATION); final String token; if (httpHeaderValue == null - || httpHeaderValue.length() <= HTTP_HEADER_VALUE_PREFIX.length()) { + || httpHeaderValue.length() <= HTTP_HEADER_VALUE_PREFIX.length()) { throw new AuthenticationProviderToken.AuthenticationException("Missing token"); } else { token = httpHeaderValue.substring(HTTP_HEADER_VALUE_PREFIX.length()); @@ -73,7 +85,7 @@ public void doFilter( List authorities = null; if (tokenProperties.adminRoles() != null - && tokenProperties.adminRoles().contains(role)) { + && tokenProperties.adminRoles().contains(role)) { authorities = Collections.singletonList(new SimpleGrantedAuthority(ROLE_ADMIN)); } diff --git a/pom.xml b/pom.xml index 39579026d..f11528c4f 100644 --- a/pom.xml +++ b/pom.xml @@ -731,6 +731,7 @@ langstream-admin-client langstream-api + langstream-auth-jwt langstream-cli langstream-core langstream-agents From 7ad04b1406e0d9cf3bcb3ce801840e6c98a7dec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 16:44:28 +0200 Subject: [PATCH 02/12] spotless --- .../pom.xml | 19 ++++++++- .../HttpAdminAuthenticationProvider.java | 39 ++++++++++++------- ...inAuthenticationProviderConfiguration.java | 4 +- .../langstream-jwt-gateway-admin-auth/pom.xml | 19 ++++++++- .../admin/JwtAdminAuthenticationProvider.java | 37 ++++++++++++------ ...inAuthenticationProviderConfiguration.java | 6 +-- .../apigateway/LangStreamApiGateway.java | 5 ++- .../GatewayAdminAuthenticationProperties.java | 2 + .../websocket/AuthenticationInterceptor.java | 20 ++++++---- .../websocket/handlers/AbstractHandler.java | 36 ++++++++--------- .../impl/GatewayRequestContextImpl.java | 16 +++++++- .../handlers/ProduceConsumeHandlerTest.java | 34 +++++++--------- .../gateway/GatewayAdminRequestContext.java | 2 - ...GatewayAuthenticationProviderRegistry.java | 2 +- .../java/ai/langstream/api/model/Gateway.java | 3 +- langstream-auth-jwt/pom.xml | 19 ++++++++- .../auth/jwt/AuthenticationProviderToken.java | 3 +- .../ai/langstream/auth/jwt/JwtProperties.java | 17 +++++++- .../primary/TokenAuthFilter.java | 21 +++++----- 19 files changed, 205 insertions(+), 99 deletions(-) diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml index 8209efae2..1d81151c3 100644 --- a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml @@ -1,11 +1,28 @@ + langstream-api-gateway-auth ai.langstream - 0.0.14-SNAPSHOT + 0.0.16-SNAPSHOT 4.0.0 diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java index 137212b32..51bd0707b 100644 --- a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java @@ -1,3 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.apigateway.auth.impl.jwt.admin; import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; @@ -20,7 +35,6 @@ public class HttpAdminAuthenticationProvider implements GatewayAdminAuthenticati private HttpAdminAuthenticationProviderConfiguration httpConfiguration; private HttpClient httpClient; - @Override public String type() { return "http"; @@ -30,11 +44,13 @@ public String type() { @SneakyThrows public void initialize(Map configuration) { httpConfiguration = - mapper.convertValue(configuration, HttpAdminAuthenticationProviderConfiguration.class); - httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(30)) - .followRedirects(HttpClient.Redirect.ALWAYS) - .build(); + mapper.convertValue( + configuration, HttpAdminAuthenticationProviderConfiguration.class); + httpClient = + HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(30)) + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); } @Override @@ -46,15 +62,11 @@ public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext conte log.info("Authenticating admin with url: {}", url); - - final HttpRequest.Builder builder = HttpRequest.newBuilder() - .uri(URI.create(url)); + final HttpRequest.Builder builder = HttpRequest.newBuilder().uri(URI.create(url)); httpConfiguration.getHeaders().forEach(builder::header); builder.header("Authorization", "Bearer " + context.credentials()); - final HttpRequest request = builder - .GET() - .build(); + final HttpRequest request = builder.GET().build(); final HttpResponse response; try { @@ -69,7 +81,8 @@ public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext conte return GatewayAuthenticationResult.authenticationSuccessful( context.adminCredentialsInputs()); } - return GatewayAuthenticationResult.authenticationFailed("Http authentication failed: " + response.statusCode()); + return GatewayAuthenticationResult.authenticationFailed( + "Http authentication failed: " + response.statusCode()); } private static String resolvePlaceholders(Map placeholders, String url) { diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java index 4b0ba8d3f..c8c19d3bb 100644 --- a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java +++ b/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java @@ -30,10 +30,12 @@ public class HttpAdminAuthenticationProviderConfiguration { @JsonAlias("base-url") private String baseUrl; + @JsonAlias("path-template") private String pathTemplate; + private Map headers = new HashMap<>(); + @JsonAlias("accepted-statuses") private List acceptedStatuses = List.of(200, 201); - } diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml index 0c9be83f0..575dd5aba 100644 --- a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml @@ -1,11 +1,28 @@ + langstream-api-gateway-auth ai.langstream - 0.0.14-SNAPSHOT + 0.0.16-SNAPSHOT 4.0.0 diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java index 382c557c5..4fc4d9149 100644 --- a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java @@ -1,3 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.apigateway.auth.impl.jwt.admin; import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; @@ -25,8 +40,8 @@ public String type() { @SneakyThrows public void initialize(Map configuration) { final JwtAdminAuthenticationProviderConfiguration tokenProperties = - mapper.convertValue(configuration, JwtAdminAuthenticationProviderConfiguration.class); - + mapper.convertValue( + configuration, JwtAdminAuthenticationProviderConfiguration.class); if (tokenProperties.adminRoles() != null) { this.adminRoles = tokenProperties.adminRoles(); @@ -34,15 +49,15 @@ public void initialize(Map configuration) { this.adminRoles = List.of(); } - - final JwtProperties jwtProperties = new JwtProperties( - tokenProperties.secretKey(), - tokenProperties.publicKey(), - tokenProperties.authClaim(), - tokenProperties.publicAlg(), - tokenProperties.audienceClaim(), - tokenProperties.audience(), - tokenProperties.jwksHostsAllowlist()); + final JwtProperties jwtProperties = + new JwtProperties( + tokenProperties.secretKey(), + tokenProperties.publicKey(), + tokenProperties.authClaim(), + tokenProperties.publicAlg(), + tokenProperties.audienceClaim(), + tokenProperties.audience(), + tokenProperties.jwksHostsAllowlist()); this.authenticationProviderToken = new AuthenticationProviderToken(jwtProperties); } diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java index 71d8498bd..68f25ab5a 100644 --- a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java +++ b/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java @@ -16,9 +16,6 @@ package ai.langstream.apigateway.auth.impl.jwt.admin; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; public record JwtAdminAuthenticationProviderConfiguration( String secretKey, @@ -28,5 +25,4 @@ public record JwtAdminAuthenticationProviderConfiguration( String audienceClaim, String audience, List adminRoles, - String jwksHostsAllowlist) { -} + String jwksHostsAllowlist) {} diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java index 42abb33cb..812e55f96 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java @@ -25,7 +25,10 @@ import org.springframework.core.env.Environment; @SpringBootApplication -@EnableConfigurationProperties({StorageProperties.class, GatewayAdminAuthenticationProperties.class}) +@EnableConfigurationProperties({ + StorageProperties.class, + GatewayAdminAuthenticationProperties.class +}) public class LangStreamApiGateway { static { diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java index f23b66b8e..6981d8f87 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java @@ -31,7 +31,9 @@ public class GatewayAdminAuthenticationProperties { private List types; + @JsonAlias("default-type") private String defaultType; + private Map> configuration = new HashMap<>(); } diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java index d1f01f6ae..648e3c268 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java @@ -47,7 +47,6 @@ public class AuthenticationInterceptor implements HandshakeInterceptor { private final Map adminAuthProviders; private final String defaultProvider; - public AuthenticationInterceptor( GatewayAdminAuthenticationProperties adminAuthenticationProperties) { final List types = adminAuthenticationProperties.getTypes(); @@ -55,12 +54,14 @@ public AuthenticationInterceptor( String defaultProvider = null; if (types != null) { for (String type : types) { - // preload all admin providers to avoid concurrency, add cache and check configuration sanity + // preload all admin providers to avoid concurrency, add cache and check + // configuration sanity final GatewayAdminAuthenticationProvider provider = GatewayAuthenticationProviderRegistry.loadAdminProvider( type, adminAuthenticationProperties.getConfiguration().get(type)); providers.put(type, provider); - if (adminAuthenticationProperties.getDefaultType() != null && adminAuthenticationProperties.getDefaultType().equals(type)) { + if (adminAuthenticationProperties.getDefaultType() != null + && adminAuthenticationProperties.getDefaultType().equals(type)) { defaultProvider = type; } } @@ -145,8 +146,12 @@ private Map authenticate(GatewayRequestContextImpl gatewayReques final GatewayAuthenticationResult result; if (gatewayRequestContext.isAdminRequest()) { if (!authentication.allowAdminRequests()) { - throw new AuthFailedException("Gateway " + gatewayRequestContext.gateway().id() - + " of tenant " + gatewayRequestContext.tenant() + " does not allow admin requests."); + throw new AuthFailedException( + "Gateway " + + gatewayRequestContext.gateway().id() + + " of tenant " + + gatewayRequestContext.tenant() + + " does not allow admin requests."); } String provider = gatewayRequestContext.adminCredentialsType(); if (provider == null && defaultProvider != null) { @@ -164,14 +169,13 @@ private Map authenticate(GatewayRequestContextImpl gatewayReques result = gatewayAdminAuthenticationProvider.authenticate(gatewayRequestContext); } else { - if (authentication != null) { + if (authentication != null) { final String provider = authentication.provider(); final GatewayAuthenticationProvider authenticationProvider = GatewayAuthenticationProviderRegistry.loadProvider( provider, authentication.configuration()); - result = - authenticationProvider.authenticate(gatewayRequestContext); + result = authenticationProvider.authenticate(gatewayRequestContext); } else { result = null; } diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java index 89dcbc36a..65b172aa6 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java @@ -18,7 +18,6 @@ import ai.langstream.api.events.EventRecord; import ai.langstream.api.events.EventSources; import ai.langstream.api.events.GatewayEventData; -import ai.langstream.api.gateway.GatewayRequestContext; import ai.langstream.api.model.Application; import ai.langstream.api.model.ApplicationSpecs; import ai.langstream.api.model.Gateway; @@ -73,8 +72,7 @@ abstract String gatewayFromPath( public void onBeforeHandshakeCompleted( AuthenticatedGatewayRequestContext gatewayRequestContext, Map attributes) - throws Exception { - } + throws Exception {} abstract void onOpen( WebSocketSession webSocketSession, @@ -183,10 +181,10 @@ private Gateway extractGateway( if (selectedGateway == null) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " is not defined in the application"); + + gatewayId + + " of type " + + type + + " is not defined in the application"); } return selectedGateway; } @@ -202,7 +200,6 @@ public GatewayRequestContextImpl validateRequest( final String adminCredentials = queryString.remove("admin-credentials"); final String adminCredentialsType = queryString.remove("admin-credentials-type"); - final Map adminCredentialsInputs = new HashMap<>(); for (Map.Entry entry : queryString.entrySet()) { @@ -219,10 +216,10 @@ public GatewayRequestContextImpl validateRequest( } else { throw new IllegalArgumentException( "invalid query parameter " - + entry.getKey() - + ". " - + "To specify a gateway parameter, use the format param:." - + "To specify a option, use the format option:."); + + entry.getKey() + + ". " + + "To specify a gateway parameter, use the format param:." + + "To specify a option, use the format option:."); } } @@ -251,8 +248,10 @@ public GatewayRequestContextImpl validateRequest( } validateOptions(options); - if (credentials != null && ( - !adminCredentialsInputs.isEmpty() || adminCredentials != null || adminCredentialsType != null)) { + if (credentials != null + && (!adminCredentialsInputs.isEmpty() + || adminCredentials != null + || adminCredentialsType != null)) { throw new IllegalArgumentException( "credentials and admin-credentials cannot be used together"); } @@ -269,7 +268,6 @@ public GatewayRequestContextImpl validateRequest( .userParameters(userParameters) .gateway(gateway) .build(); - } protected void recordCloseableResource( @@ -322,10 +320,10 @@ protected void sendEvent(EventRecord.Types type, AuthenticatedGatewayRequestCont TOPIC_CONNECTIONS_REGISTRY.getTopicConnectionsRuntime(streamingCluster); try (final TopicProducer producer = - topicConnectionsRuntime.createProducer( - "langstream-events", - streamingCluster, - Map.of("topic", gateway.eventsTopic()));) { + topicConnectionsRuntime.createProducer( + "langstream-events", + streamingCluster, + Map.of("topic", gateway.eventsTopic())); ) { producer.start(); final EventSources.GatewaySource source = diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java index 5d981aa23..399add36c 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java @@ -1,3 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.apigateway.websocket.impl; import ai.langstream.api.gateway.GatewayAdminRequestContext; @@ -21,7 +36,6 @@ public class GatewayRequestContextImpl implements GatewayAdminRequestContext { private final Map options; private final Map httpHeaders; - @Override public String tenant() { return tenant; diff --git a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java index ae39bd56b..4c45f67f1 100644 --- a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java +++ b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import ai.langstream.api.events.EventRecord; @@ -54,8 +53,6 @@ import jakarta.websocket.DeploymentException; import jakarta.websocket.Session; import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -73,7 +70,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; @@ -87,7 +83,7 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { - "spring.main.allow-bean-definition-overriding=true", + "spring.main.allow-bean-definition-overriding=true", }) @WireMockTest class ProduceConsumeHandlerTest { @@ -100,7 +96,6 @@ class ProduceConsumeHandlerTest { static List topics; static Gateways testGateways; - @TestConfiguration public static class WebSocketTestConfig { @@ -130,14 +125,18 @@ public GatewayAdminAuthenticationProperties gatewayAdminAuthenticationProperties final GatewayAdminAuthenticationProperties props = new GatewayAdminAuthenticationProperties(); props.setTypes(List.of("http")); - props.setConfiguration(Map.of("http", Map.of( - "base-url", wireMockBaseUrl, - "path-template", "/auth/{tenant}", - "headers", Map.of("h1", "v1") - ))); + props.setConfiguration( + Map.of( + "http", + Map.of( + "base-url", + wireMockBaseUrl, + "path-template", + "/auth/{tenant}", + "headers", + Map.of("h1", "v1")))); return props; } - } @NotNull @@ -547,7 +546,6 @@ void testAuthentication() { assertEquals(List.of(), user2Messages); } - @Test void testAdminAuthentication() { wireMock.register( @@ -606,15 +604,12 @@ void testAdminAuthentication() { .formatted(port)), user1Messages); - - connectAndProduce( URI.create( "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" .formatted(port)), new ProduceRequest(null, "hello user", null)); - Awaitility.await() .untilAsserted( () -> @@ -629,7 +624,8 @@ void testAdminAuthentication() { connectAndExpectClose( URI.create( "ws://localhost:%d/v1/consume/tenant1/application1/consume-no-admin?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" - .formatted(port)), new CloseReason( + .formatted(port)), + new CloseReason( CloseReason.CloseCodes.VIOLATED_POLICY, "Gateway consume-no-admin of tenant tenant1 does not allow admin requests.")); @@ -643,10 +639,10 @@ void testAdminAuthentication() { connectAndExpectClose( URI.create( "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password-but-wrong&admin-credentials-input-user-id=mock-user" - .formatted(port)), new CloseReason( + .formatted(port)), + new CloseReason( CloseReason.CloseCodes.VIOLATED_POLICY, "Gateway produce of tenant tenant1 does not allow admin requests.")); - } private record MsgRecord(Object key, Object value, Map headers) {} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java index 77ec2fcb2..d3d551d4f 100644 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java @@ -15,8 +15,6 @@ */ package ai.langstream.api.gateway; -import ai.langstream.api.model.Application; -import ai.langstream.api.model.Gateway; import java.util.Map; public interface GatewayAdminRequestContext extends GatewayRequestContext { diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java index cde68e541..cb7866bde 100644 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java @@ -59,7 +59,7 @@ public static GatewayAdminAuthenticationProvider loadAdminProvider( () -> new RuntimeException( "No GatewayAdminAuthenticationProvider found for type " - + type)) + + type)) .get(); store.initialize(configuration); return store; diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index 93b32cff3..f929e445c 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -55,7 +55,8 @@ public Gateway( this(id, type, topic, authentication, parameters, produceOptions, consumeOptions, null); } - public record Authentication(String provider, Map configuration, boolean allowAdminRequests) { + public record Authentication( + String provider, Map configuration, boolean allowAdminRequests) { public Authentication(String provider, Map configuration) { this(provider, configuration, false); } diff --git a/langstream-auth-jwt/pom.xml b/langstream-auth-jwt/pom.xml index 54ca94115..7d5b4b111 100644 --- a/langstream-auth-jwt/pom.xml +++ b/langstream-auth-jwt/pom.xml @@ -1,11 +1,28 @@ + langstream-ai ai.langstream - 0.0.14-SNAPSHOT + 0.0.16-SNAPSHOT 4.0.0 diff --git a/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java index 8252639a8..3152e96da 100644 --- a/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/AuthenticationProviderToken.java @@ -243,8 +243,7 @@ private String getTokenAudienceClaim(JwtProperties tokenProperties) return StringUtils.isNotBlank(tokenAudienceClaim) ? tokenAudienceClaim : null; } - private String getTokenAudience(JwtProperties tokenProperties) - throws IllegalArgumentException { + private String getTokenAudience(JwtProperties tokenProperties) throws IllegalArgumentException { String tokenAudience = tokenProperties.audience(); return StringUtils.isNotBlank(tokenAudience) ? tokenAudience : null; } diff --git a/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java index 6620d335b..c66919e72 100644 --- a/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java +++ b/langstream-auth-jwt/src/main/java/ai/langstream/auth/jwt/JwtProperties.java @@ -1,7 +1,20 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.auth.jwt; -import java.util.List; - public record JwtProperties( String secretKey, String publicKey, diff --git a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java b/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java index 14371d28b..cf132c71f 100644 --- a/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java +++ b/langstream-webservice/src/main/java/ai/langstream/webservice/security/infrastructure/primary/TokenAuthFilter.java @@ -48,14 +48,15 @@ public class TokenAuthFilter extends GenericFilterBean { public TokenAuthFilter(AuthTokenProperties tokenProperties) { this.tokenProperties = tokenProperties; - final JwtProperties jwtProperties = new JwtProperties( - tokenProperties.secretKey(), - tokenProperties.publicKey(), - tokenProperties.authClaim(), - tokenProperties.publicAlg(), - tokenProperties.audienceClaim(), - tokenProperties.audience(), - tokenProperties.jwksHostsAllowlist()); + final JwtProperties jwtProperties = + new JwtProperties( + tokenProperties.secretKey(), + tokenProperties.publicKey(), + tokenProperties.authClaim(), + tokenProperties.publicAlg(), + tokenProperties.audienceClaim(), + tokenProperties.audience(), + tokenProperties.jwksHostsAllowlist()); this.authenticationProvider = new AuthenticationProviderToken(jwtProperties); } @@ -69,7 +70,7 @@ public void doFilter( ((HttpServletRequest) servletRequest).getHeader(HttpHeaders.AUTHORIZATION); final String token; if (httpHeaderValue == null - || httpHeaderValue.length() <= HTTP_HEADER_VALUE_PREFIX.length()) { + || httpHeaderValue.length() <= HTTP_HEADER_VALUE_PREFIX.length()) { throw new AuthenticationProviderToken.AuthenticationException("Missing token"); } else { token = httpHeaderValue.substring(HTTP_HEADER_VALUE_PREFIX.length()); @@ -85,7 +86,7 @@ public void doFilter( List authorities = null; if (tokenProperties.adminRoles() != null - && tokenProperties.adminRoles().contains(role)) { + && tokenProperties.adminRoles().contains(role)) { authorities = Collections.singletonList(new SimpleGrantedAuthority(ROLE_ADMIN)); } From 247e7fd9de66ff7406ebce8e558d2980c8d019ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 16:45:28 +0200 Subject: [PATCH 03/12] kebab --- .../src/main/java/ai/langstream/api/model/Gateway.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index f929e445c..f90b19ab8 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -56,14 +56,14 @@ public Gateway( } public record Authentication( - String provider, Map configuration, boolean allowAdminRequests) { + String provider, Map configuration, @JsonProperty("allow-admin-requests") boolean allowAdminRequests) { public Authentication(String provider, Map configuration) { this(provider, configuration, false); } } public record KeyValueComparison( - String key, String value, String valueFromParameters, String valueFromAuthentication) { + String key, String value, @JsonAlias({"value-from-parameters"}) String valueFromParameters, @JsonAlias({"value-from-authentication"}) String valueFromAuthentication) { public static KeyValueComparison value(String key, String value) { return new KeyValueComparison(key, value, null, null); } From d13cd33b1acfd673eef135e5c2df336759b49e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 16:58:52 +0200 Subject: [PATCH 04/12] add to all examples --- .../gateway-authentication/gateways.yaml | 52 ++++++++++--------- .../openai-completions/gateways.yaml | 18 ++++--- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/examples/applications/gateway-authentication/gateways.yaml b/examples/applications/gateway-authentication/gateways.yaml index 53b62d997..90fe577d1 100644 --- a/examples/applications/gateway-authentication/gateways.yaml +++ b/examples/applications/gateway-authentication/gateways.yaml @@ -21,20 +21,20 @@ gateways: topic: input-topic parameters: - sessionId - produceOptions: + produce-options: headers: - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: consume-output-no-auth type: consume topic: output-topic parameters: - sessionId - consumeOptions: + consume-options: filters: headers: - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: produce-input-auth-google type: produce @@ -45,12 +45,13 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - produceOptions: + allow-admin-requests: true + produce-options: headers: - key: langstream-client-user-id - valueFromAuthentication: subject + value-from-authentication: subject - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: consume-output-auth-google type: consume @@ -61,13 +62,14 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - consumeOptions: + allow-admin-requests: true + consume-options: filters: headers: - key: langstream-client-user-id - valueFromAuthentication: subject + value-from-authentication: subject - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: produce-input-auth-github type: produce @@ -78,26 +80,28 @@ gateways: provider: github configuration: clientId: "{{ secrets.github.client-id }}" - produceOptions: + allow-admin-requests: true + produce-options: headers: - key: langstream-client-user-id valueFromAuthentication: login - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - - id: consume-output-auth-github - type: consume - topic: output-topic - parameters: - - sessionId - authentication: - provider: github - configuration: - clientId: "{{ secrets.github.client-id }}" - consumeOptions: + - id: consume-output-auth-github + type: consume + topic: output-topic + parameters: + - sessionId + authentication: + provider: github + configuration: + clientId: "{{ secrets.github.client-id }}" + allow-admin-requests: true + consume-options: filters: headers: - key: langstream-client-user-id - valueFromAuthentication: login + value-from-authentication: login - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId diff --git a/examples/applications/openai-completions/gateways.yaml b/examples/applications/openai-completions/gateways.yaml index 5de4c3e5c..15628a7e7 100644 --- a/examples/applications/openai-completions/gateways.yaml +++ b/examples/applications/openai-completions/gateways.yaml @@ -21,21 +21,21 @@ gateways: topic: input-topic parameters: - sessionId - produceOptions: + produce-options: headers: - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: consume-output type: consume topic: output-topic parameters: - sessionId - consumeOptions: + consume-options: filters: headers: - key: langstream-client-session-id - valueFromParameters: sessionId + value-from-parameters: sessionId - id: consume-history type: consume @@ -48,10 +48,11 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - produceOptions: + allow-admin-requests: true + produce-options: headers: - key: langstream-client-user-id - valueFromAuthentication: subject + value-from-authentication: subject - id: consume-output-auth type: consume @@ -60,9 +61,10 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - consumeOptions: + allow-admin-requests: true + consume-options: filters: headers: - key: langstream-client-user-id - valueFromAuthentication: subject + value-from-authentication: subject From 213db1b4a7214662e5803260bbf792d1b363f55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 17:53:13 +0200 Subject: [PATCH 05/12] implement Cli --- .../cli/commands/gateway/BaseGatewayCmd.java | 78 ++++++++++++++----- .../cli/commands/gateway/ChatGatewayCmd.java | 26 ++++++- .../commands/gateway/ConsumeGatewayCmd.java | 21 ++++- .../commands/gateway/ProduceGatewayCmd.java | 21 ++++- 4 files changed, 122 insertions(+), 24 deletions(-) diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java index c5e41948f..c37e5e9a7 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java @@ -34,7 +34,8 @@ public abstract class BaseGatewayCmd extends BaseCmd { protected static final ObjectMapper messageMapper = new ObjectMapper(); - @CommandLine.ParentCommand private RootGatewayCmd cmd; + @CommandLine.ParentCommand + private RootGatewayCmd cmd; @Override protected RootCmd getRootCmd() { @@ -42,9 +43,10 @@ protected RootCmd getRootCmd() { } private static String computeQueryString( - String credentials, Map userParams, Map options) { + Map systemParams, Map userParams, Map options) { String paramsPart = ""; String optionsPart = ""; + String systemParamsPart = ""; if (userParams != null) { paramsPart = userParams.entrySet().stream() @@ -59,12 +61,16 @@ private static String computeQueryString( .collect(Collectors.joining("&")); } - String credentialsPart = ""; - if (credentials != null) { - credentialsPart = encodeParam("credentials", credentials, ""); + + if (systemParams != null) { + systemParamsPart = + systemParams.entrySet().stream() + .map(e -> encodeParam(e, "")) + .collect(Collectors.joining("&")); } - return String.join("&", List.of(credentialsPart, paramsPart, optionsPart)); + + return String.join("&", List.of(systemParamsPart, paramsPart, optionsPart)); } private static String encodeParam(Map.Entry e, String prefix) { @@ -83,8 +89,29 @@ protected String validateGatewayAndGetUrl( String type, Map params, Map options, - String credentials) { - validateGateway(applicationId, gatewayId, type, params, options, credentials); + String credentials, + String adminCredentials, + String adminCredentialsType, + Map adminCredentialsInputs) { + validateGateway(applicationId, gatewayId, type, params, options, credentials, adminCredentials, + adminCredentialsType, adminCredentialsInputs); + + Map systemParams = new HashMap<>(); + if (credentials != null) { + systemParams.put("credentials", credentials); + } + if (adminCredentials != null) { + systemParams.put("admin-credentials", adminCredentials); + } + if (adminCredentialsType != null) { + systemParams.put("admin-credentials-type", adminCredentialsType); + } + if (adminCredentialsInputs != null) { + for (Map.Entry adminInput : adminCredentialsInputs.entrySet()) { + systemParams.put("admin-credentials-input-" + adminInput.getKey(), adminInput.getValue()); + } + } + return String.format( "%s/v1/%s/%s/%s/%s?%s", getApiGatewayUrl(), @@ -92,7 +119,7 @@ protected String validateGatewayAndGetUrl( getTenant(), applicationId, gatewayId, - computeQueryString(credentials, params, options)); + computeQueryString(systemParams, params, options)); } private String getTenant() { @@ -112,7 +139,10 @@ protected void validateGateway( String type, Map params, Map options, - String credentials) { + String credentials, + String adminCredentials, + String adminCredentialsType, + Map adminCredentialsInputs) { final AdminClient client = getClient(); @@ -136,10 +166,10 @@ protected void validateGateway( if (selectedGateway == null) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " is not defined in the application"); + + gatewayId + + " of type " + + type + + " is not defined in the application"); } final List requiredParameters = selectedGateway.getParameters(); if (requiredParameters != null) { @@ -147,19 +177,27 @@ protected void validateGateway( if (params == null || !params.containsKey(requiredParameter)) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " requires parameter " - + requiredParameter); + + gatewayId + + " of type " + + type + + " requires parameter " + + requiredParameter); } } } if (selectedGateway.getAuthentication() != null) { - if (credentials == null) { + if (credentials == null && adminCredentials == null) { throw new IllegalArgumentException( "gateway " + gatewayId + " of type " + type + " requires credentials"); } + if (adminCredentials != null) { + final Object allowAdminRequests = selectedGateway.getAuthentication().get("allow-admin-requests"); + if (allowAdminRequests != null && allowAdminRequests.toString().equals("false")) { + throw new IllegalArgumentException( + "gateway " + gatewayId + " of type " + type + " do not allow admin requests"); + + } + } } } } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java index 48f9eea52..12b458d8d 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java @@ -67,6 +67,22 @@ public class ChatGatewayCmd extends BaseGatewayCmd { description = "Connect timeout for WebSocket connections in seconds.") private long connectTimeoutSeconds = 0; + @CommandLine.Option( + names = {"-ac", "--admin-credentials"}, + description = + "Admin credentials for the gateway.") + private String adminCredentials; + + @CommandLine.Option( + names = {"-act", "--admin-credentials-type"}, + description = "Admin credentials type for the gateway.") + private String adminCredentialsType; + + @CommandLine.Option( + names = {"-aci", "--admin-credentials-input"}, + description = "Admin credentials type for the gateway.") + private Map adminCredentialsInputs; + @Override @SneakyThrows public void run() { @@ -78,7 +94,10 @@ public void run() { Gateways.Gateway.TYPE_CONSUME, params, consumeGatewayOptions, - credentials); + credentials, + adminCredentials, + adminCredentialsType, + adminCredentialsInputs); final String producePath = validateGatewayAndGetUrl( applicationId, @@ -86,7 +105,10 @@ public void run() { Gateways.Gateway.TYPE_PRODUCE, params, Map.of(), - credentials); + credentials, + adminCredentials, + adminCredentialsType, + adminCredentialsInputs); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java index e33d140d4..e044b4d79 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java @@ -46,6 +46,22 @@ public class ConsumeGatewayCmd extends BaseGatewayCmd { "Credentials for the gateway. Required if the gateway requires authentication.") private String credentials; + @CommandLine.Option( + names = {"-ac", "--admin-credentials"}, + description = + "Admin credentials for the gateway.") + private String adminCredentials; + + @CommandLine.Option( + names = {"-act", "--admin-credentials-type"}, + description = "Admin credentials type for the gateway.") + private String adminCredentialsType; + + @CommandLine.Option( + names = {"-aci", "--admin-credentials-input"}, + description = "Admin credentials type for the gateway.") + private Map adminCredentialsInputs; + @CommandLine.Option( names = {"--position"}, description = @@ -77,7 +93,10 @@ public void run() { Gateways.Gateway.TYPE_CONSUME, params, options, - credentials); + credentials, + adminCredentials, + adminCredentialsType, + adminCredentialsInputs); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java index b1d57e274..1ab6eadd1 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java @@ -77,6 +77,22 @@ static class ProduceRequest { description = "Connect timeout for WebSocket connections in seconds.") private long connectTimeoutSeconds = 0; + @CommandLine.Option( + names = {"-ac", "--admin-credentials"}, + description = + "Admin credentials for the gateway.") + private String adminCredentials; + + @CommandLine.Option( + names = {"-act", "--admin-credentials-type"}, + description = "Admin credentials type for the gateway.") + private String adminCredentialsType; + + @CommandLine.Option( + names = {"-aci", "--admin-credentials-input"}, + description = "Admin credentials type for the gateway.") + private Map adminCredentialsInputs; + @Override @SneakyThrows public void run() { @@ -87,7 +103,10 @@ public void run() { Gateways.Gateway.TYPE_PRODUCE, params, Map.of(), - credentials); + credentials, + adminCredentials, + adminCredentialsType, + adminCredentialsInputs); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; CountDownLatch countDownLatch = new CountDownLatch(1); From 8f707243dd65abc9a0ac261a7c2f7f9cd5a30701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Fri, 8 Sep 2023 17:53:31 +0200 Subject: [PATCH 06/12] spotless --- .../java/ai/langstream/api/model/Gateway.java | 9 +++- .../cli/commands/gateway/BaseGatewayCmd.java | 52 ++++++++++++------- .../cli/commands/gateway/ChatGatewayCmd.java | 3 +- .../commands/gateway/ConsumeGatewayCmd.java | 3 +- .../commands/gateway/ProduceGatewayCmd.java | 3 +- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index f90b19ab8..dd631d6d5 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -56,14 +56,19 @@ public Gateway( } public record Authentication( - String provider, Map configuration, @JsonProperty("allow-admin-requests") boolean allowAdminRequests) { + String provider, + Map configuration, + @JsonProperty("allow-admin-requests") boolean allowAdminRequests) { public Authentication(String provider, Map configuration) { this(provider, configuration, false); } } public record KeyValueComparison( - String key, String value, @JsonAlias({"value-from-parameters"}) String valueFromParameters, @JsonAlias({"value-from-authentication"}) String valueFromAuthentication) { + String key, + String value, + @JsonAlias({"value-from-parameters"}) String valueFromParameters, + @JsonAlias({"value-from-authentication"}) String valueFromAuthentication) { public static KeyValueComparison value(String key, String value) { return new KeyValueComparison(key, value, null, null); } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java index c37e5e9a7..f8509aa6a 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java @@ -34,8 +34,7 @@ public abstract class BaseGatewayCmd extends BaseCmd { protected static final ObjectMapper messageMapper = new ObjectMapper(); - @CommandLine.ParentCommand - private RootGatewayCmd cmd; + @CommandLine.ParentCommand private RootGatewayCmd cmd; @Override protected RootCmd getRootCmd() { @@ -43,7 +42,9 @@ protected RootCmd getRootCmd() { } private static String computeQueryString( - Map systemParams, Map userParams, Map options) { + Map systemParams, + Map userParams, + Map options) { String paramsPart = ""; String optionsPart = ""; String systemParamsPart = ""; @@ -61,7 +62,6 @@ private static String computeQueryString( .collect(Collectors.joining("&")); } - if (systemParams != null) { systemParamsPart = systemParams.entrySet().stream() @@ -69,7 +69,6 @@ private static String computeQueryString( .collect(Collectors.joining("&")); } - return String.join("&", List.of(systemParamsPart, paramsPart, optionsPart)); } @@ -93,8 +92,16 @@ protected String validateGatewayAndGetUrl( String adminCredentials, String adminCredentialsType, Map adminCredentialsInputs) { - validateGateway(applicationId, gatewayId, type, params, options, credentials, adminCredentials, - adminCredentialsType, adminCredentialsInputs); + validateGateway( + applicationId, + gatewayId, + type, + params, + options, + credentials, + adminCredentials, + adminCredentialsType, + adminCredentialsInputs); Map systemParams = new HashMap<>(); if (credentials != null) { @@ -108,7 +115,8 @@ protected String validateGatewayAndGetUrl( } if (adminCredentialsInputs != null) { for (Map.Entry adminInput : adminCredentialsInputs.entrySet()) { - systemParams.put("admin-credentials-input-" + adminInput.getKey(), adminInput.getValue()); + systemParams.put( + "admin-credentials-input-" + adminInput.getKey(), adminInput.getValue()); } } @@ -166,10 +174,10 @@ protected void validateGateway( if (selectedGateway == null) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " is not defined in the application"); + + gatewayId + + " of type " + + type + + " is not defined in the application"); } final List requiredParameters = selectedGateway.getParameters(); if (requiredParameters != null) { @@ -177,11 +185,11 @@ protected void validateGateway( if (params == null || !params.containsKey(requiredParameter)) { throw new IllegalArgumentException( "gateway " - + gatewayId - + " of type " - + type - + " requires parameter " - + requiredParameter); + + gatewayId + + " of type " + + type + + " requires parameter " + + requiredParameter); } } } @@ -191,11 +199,15 @@ protected void validateGateway( "gateway " + gatewayId + " of type " + type + " requires credentials"); } if (adminCredentials != null) { - final Object allowAdminRequests = selectedGateway.getAuthentication().get("allow-admin-requests"); + final Object allowAdminRequests = + selectedGateway.getAuthentication().get("allow-admin-requests"); if (allowAdminRequests != null && allowAdminRequests.toString().equals("false")) { throw new IllegalArgumentException( - "gateway " + gatewayId + " of type " + type + " do not allow admin requests"); - + "gateway " + + gatewayId + + " of type " + + type + + " do not allow admin requests"); } } } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java index 12b458d8d..60b7c6518 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java @@ -69,8 +69,7 @@ public class ChatGatewayCmd extends BaseGatewayCmd { @CommandLine.Option( names = {"-ac", "--admin-credentials"}, - description = - "Admin credentials for the gateway.") + description = "Admin credentials for the gateway.") private String adminCredentials; @CommandLine.Option( diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java index e044b4d79..1e3e7b068 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java @@ -48,8 +48,7 @@ public class ConsumeGatewayCmd extends BaseGatewayCmd { @CommandLine.Option( names = {"-ac", "--admin-credentials"}, - description = - "Admin credentials for the gateway.") + description = "Admin credentials for the gateway.") private String adminCredentials; @CommandLine.Option( diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java index 1ab6eadd1..c0b33ab95 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java @@ -79,8 +79,7 @@ static class ProduceRequest { @CommandLine.Option( names = {"-ac", "--admin-credentials"}, - description = - "Admin credentials for the gateway.") + description = "Admin credentials for the gateway.") private String adminCredentials; @CommandLine.Option( From c0a8df30c0a9961f48f6980e70e66cd3cf158fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 11 Sep 2023 10:53:54 +0200 Subject: [PATCH 07/12] change name and others --- .../gateway-authentication/gateways.yaml | 4 - .../openai-completions/gateways.yaml | 2 - .../github/GitHubAuthenticationProvider.java | 4 +- .../pom.xml | 2 +- .../HttpAdminAuthenticationProvider.java | 11 +- ...inAuthenticationProviderConfiguration.java | 0 ...api.gateway.GatewayAuthenticationProvider} | 0 .../pom.xml | 2 +- .../admin/JwtAdminAuthenticationProvider.java | 11 +- ...inAuthenticationProviderConfiguration.java | 0 ...api.gateway.GatewayAuthenticationProvider} | 0 langstream-api-gateway-auth/pom.xml | 4 +- langstream-api-gateway/pom.xml | 4 +- .../apigateway/LangStreamApiGateway.java | 7 +- ... GatewayTestAuthenticationProperties.java} | 13 +- .../websocket/AuthenticationInterceptor.java | 174 ++++++------------ .../apigateway/websocket/WebSocketConfig.java | 4 +- .../websocket/handlers/AbstractHandler.java | 25 +-- .../websocket/handlers/ProduceHandler.java | 3 +- ...uthenticatedGatewayRequestContextImpl.java | 86 +++++++++ .../impl/GatewayRequestContextImpl.java | 32 +--- .../TestGatewayAuthenticationProvider.java | 2 +- .../handlers/ProduceConsumeHandlerTest.java | 61 +++--- .../GatewayAdminAuthenticationProvider.java | 27 --- .../gateway/GatewayAdminRequestContext.java | 25 --- ...GatewayAuthenticationProviderRegistry.java | 22 --- .../api/gateway/GatewayRequestContext.java | 2 + .../java/ai/langstream/api/model/Gateway.java | 4 +- .../cli/commands/gateway/BaseGatewayCmd.java | 44 ++--- .../cli/commands/gateway/ChatGatewayCmd.java | 28 +-- .../commands/gateway/ConsumeGatewayCmd.java | 20 +- .../commands/gateway/ProduceGatewayCmd.java | 20 +- .../ApplicationPlaceholderResolver.java | 2 +- 33 files changed, 249 insertions(+), 396 deletions(-) rename langstream-api-gateway-auth/{langstream-http-gateway-admin-auth => langstream-http-api-gateway-auth}/pom.xml (96%) rename langstream-api-gateway-auth/{langstream-http-gateway-admin-auth => langstream-http-api-gateway-auth}/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java (91%) rename langstream-api-gateway-auth/{langstream-http-gateway-admin-auth => langstream-http-api-gateway-auth}/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java (100%) rename langstream-api-gateway-auth/{langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider => langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider} (100%) rename langstream-api-gateway-auth/{langstream-jwt-gateway-admin-auth => langstream-jwt-api-gateway-auth}/pom.xml (95%) rename langstream-api-gateway-auth/{langstream-jwt-gateway-admin-auth => langstream-jwt-api-gateway-auth}/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java (88%) rename langstream-api-gateway-auth/{langstream-jwt-gateway-admin-auth => langstream-jwt-api-gateway-auth}/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java (100%) rename langstream-api-gateway-auth/{langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider => langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider} (100%) rename langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/{GatewayAdminAuthenticationProperties.java => GatewayTestAuthenticationProperties.java} (70%) create mode 100644 langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/AuthenticatedGatewayRequestContextImpl.java delete mode 100644 langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java delete mode 100644 langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java diff --git a/examples/applications/gateway-authentication/gateways.yaml b/examples/applications/gateway-authentication/gateways.yaml index 90fe577d1..6e57a538c 100644 --- a/examples/applications/gateway-authentication/gateways.yaml +++ b/examples/applications/gateway-authentication/gateways.yaml @@ -45,7 +45,6 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - allow-admin-requests: true produce-options: headers: - key: langstream-client-user-id @@ -62,7 +61,6 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - allow-admin-requests: true consume-options: filters: headers: @@ -80,7 +78,6 @@ gateways: provider: github configuration: clientId: "{{ secrets.github.client-id }}" - allow-admin-requests: true produce-options: headers: - key: langstream-client-user-id @@ -97,7 +94,6 @@ gateways: provider: github configuration: clientId: "{{ secrets.github.client-id }}" - allow-admin-requests: true consume-options: filters: headers: diff --git a/examples/applications/openai-completions/gateways.yaml b/examples/applications/openai-completions/gateways.yaml index 15628a7e7..eda8d4ea8 100644 --- a/examples/applications/openai-completions/gateways.yaml +++ b/examples/applications/openai-completions/gateways.yaml @@ -48,7 +48,6 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - allow-admin-requests: true produce-options: headers: - key: langstream-client-user-id @@ -61,7 +60,6 @@ gateways: provider: google configuration: clientId: "{{ secrets.google.client-id }}" - allow-admin-requests: true consume-options: filters: headers: diff --git a/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java index 137f6f47f..8733af54f 100644 --- a/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java @@ -30,7 +30,7 @@ @Slf4j public class GitHubAuthenticationProvider implements GatewayAuthenticationProvider { - private static final ObjectMapper mapper = new ObjectMapper(); + protected static final ObjectMapper mapper = new ObjectMapper(); private String clientId; @@ -81,7 +81,7 @@ public GatewayAuthenticationResult authenticate(GatewayRequestContext context) { log.info("X-OAuth-Client-Id: {}", responseClientId); log.info("Required: X-OAuth-Client-Id: {}", clientId); - Map result = new ObjectMapper().readValue(body, Map.class); + Map result = mapper.readValue(body, Map.class); if (log.isDebugEnabled()) { response.headers().map().forEach((k, v) -> log.debug("Header {}: {}", k, v)); } diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/pom.xml similarity index 96% rename from langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/pom.xml index 1d81151c3..f356e9dfe 100644 --- a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/pom.xml +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - langstream-http-gateway-admin-auth + langstream-http-api-gateway-auth diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java similarity index 91% rename from langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java index 51bd0707b..dc9cc8bb3 100644 --- a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java @@ -15,9 +15,9 @@ */ package ai.langstream.apigateway.auth.impl.jwt.admin; -import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; -import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.gateway.GatewayAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationResult; +import ai.langstream.api.gateway.GatewayRequestContext; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; @@ -29,7 +29,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class HttpAdminAuthenticationProvider implements GatewayAdminAuthenticationProvider { +public class HttpAdminAuthenticationProvider implements GatewayAuthenticationProvider { private static final ObjectMapper mapper = new ObjectMapper(); private HttpAdminAuthenticationProviderConfiguration httpConfiguration; @@ -54,7 +54,7 @@ public void initialize(Map configuration) { } @Override - public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context) { + public GatewayAuthenticationResult authenticate(GatewayRequestContext context) { final Map placeholders = Map.of("tenant", context.tenant()); final String uri = resolvePlaceholders(placeholders, httpConfiguration.getPathTemplate()); @@ -78,8 +78,7 @@ public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext conte return GatewayAuthenticationResult.authenticationFailed(e.getMessage()); } if (httpConfiguration.getAcceptedStatuses().contains(response.statusCode())) { - return GatewayAuthenticationResult.authenticationSuccessful( - context.adminCredentialsInputs()); + return GatewayAuthenticationResult.authenticationSuccessful(Map.of()); } return GatewayAuthenticationResult.authenticationFailed( "Http authentication failed: " + response.statusCode()); diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java similarity index 100% rename from langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java diff --git a/langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider similarity index 100% rename from langstream-api-gateway-auth/langstream-http-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/pom.xml similarity index 95% rename from langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/pom.xml index 575dd5aba..0d2bad396 100644 --- a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/pom.xml +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - langstream-jwt-gateway-admin-auth + langstream-jwt-api-gateway-auth diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java similarity index 88% rename from langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java index 4fc4d9149..e18963293 100644 --- a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java @@ -15,9 +15,9 @@ */ package ai.langstream.apigateway.auth.impl.jwt.admin; -import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; -import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.gateway.GatewayAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationResult; +import ai.langstream.api.gateway.GatewayRequestContext; import ai.langstream.auth.jwt.AuthenticationProviderToken; import ai.langstream.auth.jwt.JwtProperties; import com.fasterxml.jackson.databind.ObjectMapper; @@ -25,7 +25,7 @@ import java.util.Map; import lombok.SneakyThrows; -public class JwtAdminAuthenticationProvider implements GatewayAdminAuthenticationProvider { +public class JwtAdminAuthenticationProvider implements GatewayAuthenticationProvider { private static final ObjectMapper mapper = new ObjectMapper(); private AuthenticationProviderToken authenticationProviderToken; @@ -62,7 +62,7 @@ public void initialize(Map configuration) { } @Override - public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context) { + public GatewayAuthenticationResult authenticate(GatewayRequestContext context) { String role; try { role = authenticationProviderToken.authenticate(context.credentials()); @@ -72,7 +72,6 @@ public GatewayAuthenticationResult authenticate(GatewayAdminRequestContext conte if (!adminRoles.contains(role)) { return GatewayAuthenticationResult.authenticationFailed("Not an admin."); } - return GatewayAuthenticationResult.authenticationSuccessful( - context.adminCredentialsInputs()); + return GatewayAuthenticationResult.authenticationSuccessful(Map.of()); } } diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java similarity index 100% rename from langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java diff --git a/langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider similarity index 100% rename from langstream-api-gateway-auth/langstream-jwt-gateway-admin-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAdminAuthenticationProvider rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider diff --git a/langstream-api-gateway-auth/pom.xml b/langstream-api-gateway-auth/pom.xml index 6f3c22932..d31e9864f 100644 --- a/langstream-api-gateway-auth/pom.xml +++ b/langstream-api-gateway-auth/pom.xml @@ -30,7 +30,7 @@ langstream-google-api-gateway-auth langstream-github-api-gateway-auth - langstream-jwt-gateway-admin-auth - langstream-http-gateway-admin-auth + langstream-jwt-api-gateway-auth + langstream-http-api-gateway-auth diff --git a/langstream-api-gateway/pom.xml b/langstream-api-gateway/pom.xml index 4853cc311..77329e93d 100644 --- a/langstream-api-gateway/pom.xml +++ b/langstream-api-gateway/pom.xml @@ -131,14 +131,14 @@ ai.langstream - langstream-jwt-gateway-admin-auth + langstream-jwt-api-gateway-auth ${project.version} runtime ai.langstream - langstream-http-gateway-admin-auth + langstream-http-api-gateway-auth ${project.version} runtime diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java index 812e55f96..c3c47e2dd 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/LangStreamApiGateway.java @@ -15,7 +15,7 @@ */ package ai.langstream.apigateway; -import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; +import ai.langstream.apigateway.config.GatewayTestAuthenticationProperties; import ai.langstream.apigateway.config.StorageProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,10 +25,7 @@ import org.springframework.core.env.Environment; @SpringBootApplication -@EnableConfigurationProperties({ - StorageProperties.class, - GatewayAdminAuthenticationProperties.class -}) +@EnableConfigurationProperties({StorageProperties.class, GatewayTestAuthenticationProperties.class}) public class LangStreamApiGateway { static { diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayTestAuthenticationProperties.java similarity index 70% rename from langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java rename to langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayTestAuthenticationProperties.java index 6981d8f87..112e21453 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayAdminAuthenticationProperties.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/config/GatewayTestAuthenticationProperties.java @@ -15,25 +15,20 @@ */ package ai.langstream.apigateway.config; -import com.fasterxml.jackson.annotation.JsonAlias; import java.util.HashMap; -import java.util.List; import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "application.gateways.auth.admin") +@ConfigurationProperties(prefix = "application.gateways.auth.test") @Data @NoArgsConstructor @AllArgsConstructor -public class GatewayAdminAuthenticationProperties { +public class GatewayTestAuthenticationProperties { - private List types; + private String type; - @JsonAlias("default-type") - private String defaultType; - - private Map> configuration = new HashMap<>(); + private Map configuration = new HashMap<>(); } diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java index 648e3c268..406b81150 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java @@ -15,22 +15,20 @@ */ package ai.langstream.apigateway.websocket; -import ai.langstream.api.gateway.GatewayAdminAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationProvider; import ai.langstream.api.gateway.GatewayAuthenticationProviderRegistry; import ai.langstream.api.gateway.GatewayAuthenticationResult; import ai.langstream.api.gateway.GatewayRequestContext; -import ai.langstream.api.model.Application; import ai.langstream.api.model.Gateway; -import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; +import ai.langstream.apigateway.config.GatewayTestAuthenticationProperties; import ai.langstream.apigateway.websocket.handlers.AbstractHandler; -import ai.langstream.apigateway.websocket.impl.GatewayRequestContextImpl; +import ai.langstream.apigateway.websocket.impl.AuthenticatedGatewayRequestContextImpl; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; -import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -44,33 +42,22 @@ @Slf4j public class AuthenticationInterceptor implements HandshakeInterceptor { - private final Map adminAuthProviders; - private final String defaultProvider; + private final GatewayAuthenticationProvider authTestProvider; public AuthenticationInterceptor( - GatewayAdminAuthenticationProperties adminAuthenticationProperties) { - final List types = adminAuthenticationProperties.getTypes(); - Map providers = new HashMap<>(); - String defaultProvider = null; - if (types != null) { - for (String type : types) { - // preload all admin providers to avoid concurrency, add cache and check - // configuration sanity - final GatewayAdminAuthenticationProvider provider = - GatewayAuthenticationProviderRegistry.loadAdminProvider( - type, adminAuthenticationProperties.getConfiguration().get(type)); - providers.put(type, provider); - if (adminAuthenticationProperties.getDefaultType() != null - && adminAuthenticationProperties.getDefaultType().equals(type)) { - defaultProvider = type; - } - } - if (types.size() == 1) { - defaultProvider = types.get(0); - } + GatewayTestAuthenticationProperties testAuthenticationProperties) { + if (testAuthenticationProperties.getType() != null) { + authTestProvider = + GatewayAuthenticationProviderRegistry.loadProvider( + testAuthenticationProperties.getType(), + testAuthenticationProperties.getConfiguration()); + log.info( + "Loaded test authentication provider {}", + authTestProvider.getClass().getName()); + } else { + authTestProvider = null; + log.info("No test authentication provider configured"); } - this.adminAuthProviders = providers; - this.defaultProvider = defaultProvider; } @Override @@ -94,7 +81,7 @@ public boolean beforeHandshake( final String path = httpRequest.getURI().getPath(); final Map vars = antPathMatcher.extractUriTemplateVariables(handler.path(), path); - final GatewayRequestContextImpl gatewayRequestContext = + final GatewayRequestContext gatewayRequestContext = handler.validateRequest( vars, querystring, request.getHeaders().toSingleValueMap()); @@ -133,7 +120,7 @@ public AuthFailedException(String message) { } } - private Map authenticate(GatewayRequestContextImpl gatewayRequestContext) + private Map authenticate(GatewayRequestContext gatewayRequestContext) throws AuthFailedException { final Gateway.Authentication authentication = @@ -144,110 +131,71 @@ private Map authenticate(GatewayRequestContextImpl gatewayReques } final GatewayAuthenticationResult result; - if (gatewayRequestContext.isAdminRequest()) { - if (!authentication.allowAdminRequests()) { + if (gatewayRequestContext.isTestMode()) { + if (!authentication.allowTestMode()) { throw new AuthFailedException( "Gateway " + gatewayRequestContext.gateway().id() + " of tenant " + gatewayRequestContext.tenant() - + " does not allow admin requests."); - } - String provider = gatewayRequestContext.adminCredentialsType(); - if (provider == null && defaultProvider != null) { - provider = defaultProvider; + + " does not allow test mode."); } - if (provider == null) { - throw new AuthFailedException("No admin auth provider specified"); + if (authTestProvider == null) { + throw new AuthFailedException("No test auth provider specified"); } - - final GatewayAdminAuthenticationProvider gatewayAdminAuthenticationProvider = - adminAuthProviders.get(provider); - if (gatewayAdminAuthenticationProvider == null) { - throw new AuthFailedException("Unknown admin auth provider " + provider); - } - result = gatewayAdminAuthenticationProvider.authenticate(gatewayRequestContext); + result = authTestProvider.authenticate(gatewayRequestContext); } else { - - if (authentication != null) { - final String provider = authentication.provider(); - - final GatewayAuthenticationProvider authenticationProvider = - GatewayAuthenticationProviderRegistry.loadProvider( - provider, authentication.configuration()); - result = authenticationProvider.authenticate(gatewayRequestContext); - } else { - result = null; - } + final String provider = authentication.provider(); + final GatewayAuthenticationProvider authProvider = + GatewayAuthenticationProviderRegistry.loadProvider( + provider, authentication.configuration()); + result = authProvider.authenticate(gatewayRequestContext); } if (result == null) { - return Map.of(); + throw new AuthFailedException("Authentication provider returned null"); } if (!result.authenticated()) { throw new AuthFailedException(result.reason()); } - final Map values = result.principalValues(); - if (values == null) { - return Map.of(); + return getPrincipalValues(result, gatewayRequestContext); + } + + private Map getPrincipalValues( + GatewayAuthenticationResult result, GatewayRequestContext context) { + if (!context.isTestMode()) { + final Map values = result.principalValues(); + if (values == null) { + return Map.of(); + } + return values; + } else { + final Map values = new HashMap<>(); + final String principalSubject = DigestUtils.sha256Hex(context.credentials()); + final int principalNumericId = principalSubject.hashCode(); + final String principalEmail = "%s@locahost".formatted(principalSubject); + + // google + values.putIfAbsent("subject", principalSubject); + values.putIfAbsent("email", principalEmail); + values.putIfAbsent("name", principalSubject); + + // github + values.putIfAbsent("login", principalSubject); + values.putIfAbsent("id", principalNumericId + ""); + return values; } - return values; } private AuthenticatedGatewayRequestContext getAuthenticatedGatewayRequestContext( GatewayRequestContext gatewayRequestContext, Map principalValues, Map attributes) { - return new AuthenticatedGatewayRequestContext() { - @Override - public Map principalValues() { - return principalValues; - } - @Override - public String tenant() { - return gatewayRequestContext.tenant(); - } - - @Override - public Map attributes() { - return attributes; - } - - @Override - public String applicationId() { - return gatewayRequestContext.applicationId(); - } - - @Override - public Application application() { - return gatewayRequestContext.application(); - } - - @Override - public Gateway gateway() { - return gatewayRequestContext.gateway(); - } - - @Override - public String credentials() { - return gatewayRequestContext.credentials(); - } - - @Override - public Map userParameters() { - return gatewayRequestContext.userParameters(); - } - - @Override - public Map options() { - return gatewayRequestContext.options(); - } - - @Override - public Map httpHeaders() { - return gatewayRequestContext.httpHeaders(); - } - }; + return AuthenticatedGatewayRequestContextImpl.builder() + .gatewayRequestContext(gatewayRequestContext) + .attributes(attributes) + .principalValues(principalValues) + .build(); } @Override diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java index ae876c749..3f9b780e5 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/WebSocketConfig.java @@ -16,7 +16,7 @@ package ai.langstream.apigateway.websocket; import ai.langstream.api.storage.ApplicationStore; -import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; +import ai.langstream.apigateway.config.GatewayTestAuthenticationProperties; import ai.langstream.apigateway.websocket.handlers.ConsumeHandler; import ai.langstream.apigateway.websocket.handlers.ProduceHandler; import jakarta.annotation.PreDestroy; @@ -42,7 +42,7 @@ public class WebSocketConfig implements WebSocketConfigurer { public static final String PRODUCE_PATH = "/v1/produce/{tenant}/{application}/{gateway}"; private final ApplicationStore applicationStore; - private final GatewayAdminAuthenticationProperties adminAuthenticationProperties; + private final GatewayTestAuthenticationProperties adminAuthenticationProperties; private final ExecutorService consumeThreadPool = Executors.newCachedThreadPool(); @Override diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java index 65b172aa6..abfefa7fb 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/AbstractHandler.java @@ -18,6 +18,7 @@ import ai.langstream.api.events.EventRecord; import ai.langstream.api.events.EventSources; import ai.langstream.api.events.GatewayEventData; +import ai.langstream.api.gateway.GatewayRequestContext; import ai.langstream.api.model.Application; import ai.langstream.api.model.ApplicationSpecs; import ai.langstream.api.model.Gateway; @@ -189,7 +190,7 @@ private Gateway extractGateway( return selectedGateway; } - public GatewayRequestContextImpl validateRequest( + public GatewayRequestContext validateRequest( Map pathVars, Map queryString, Map httpHeaders) { @@ -197,18 +198,9 @@ public GatewayRequestContextImpl validateRequest( Map userParameters = new HashMap<>(); final String credentials = queryString.remove("credentials"); - final String adminCredentials = queryString.remove("admin-credentials"); - final String adminCredentialsType = queryString.remove("admin-credentials-type"); - - final Map adminCredentialsInputs = new HashMap<>(); + final String testCredentials = queryString.remove("test-credentials"); for (Map.Entry entry : queryString.entrySet()) { - if (entry.getKey().startsWith("admin-credentials-input-")) { - adminCredentialsInputs.put( - entry.getKey().substring("admin-credentials-input-".length()), - entry.getValue()); - continue; - } if (entry.getKey().startsWith("option:")) { options.put(entry.getKey().substring("option:".length()), entry.getValue()); } else if (entry.getKey().startsWith("param:")) { @@ -248,21 +240,16 @@ public GatewayRequestContextImpl validateRequest( } validateOptions(options); - if (credentials != null - && (!adminCredentialsInputs.isEmpty() - || adminCredentials != null - || adminCredentialsType != null)) { + if (credentials != null && testCredentials != null) { throw new IllegalArgumentException( - "credentials and admin-credentials cannot be used together"); + "credentials and test-credentials cannot be used together"); } return GatewayRequestContextImpl.builder() .tenant(tenant) .applicationId(applicationId) .application(application) .credentials(credentials) - .adminCredentialsType(adminCredentialsType) - .adminCredentials(adminCredentials) - .adminCredentialsInputs(adminCredentialsInputs) + .testCredentials(testCredentials) .httpHeaders(httpHeaders) .options(options) .userParameters(userParameters) diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/ProduceHandler.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/ProduceHandler.java index aa1efd5f7..98f55d3af 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/ProduceHandler.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/handlers/ProduceHandler.java @@ -228,7 +228,8 @@ private List
getCommonHeaders( value = principalValues.get(mapping.valueFromAuthentication()); } if (value == null) { - throw new IllegalArgumentException(mapping.key() + "header cannot be empty"); + throw new IllegalArgumentException( + "header " + mapping.key() + " cannot be empty"); } headers.add(SimpleRecord.SimpleHeader.of(mapping.key(), value)); diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/AuthenticatedGatewayRequestContextImpl.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/AuthenticatedGatewayRequestContextImpl.java new file mode 100644 index 000000000..9c75f47ce --- /dev/null +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/AuthenticatedGatewayRequestContextImpl.java @@ -0,0 +1,86 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.apigateway.websocket.impl; + +import ai.langstream.api.gateway.GatewayRequestContext; +import ai.langstream.api.model.Application; +import ai.langstream.api.model.Gateway; +import ai.langstream.apigateway.websocket.AuthenticatedGatewayRequestContext; +import java.util.Map; +import lombok.Builder; + +@Builder +public class AuthenticatedGatewayRequestContextImpl implements AuthenticatedGatewayRequestContext { + + private final GatewayRequestContext gatewayRequestContext; + private final Map attributes; + private final Map principalValues; + + @Override + public Map attributes() { + return attributes; + } + + @Override + public Map principalValues() { + return principalValues; + } + + @Override + public String tenant() { + return gatewayRequestContext.tenant(); + } + + @Override + public String applicationId() { + return gatewayRequestContext.applicationId(); + } + + @Override + public Application application() { + return gatewayRequestContext.application(); + } + + @Override + public Gateway gateway() { + return gatewayRequestContext.gateway(); + } + + @Override + public String credentials() { + return gatewayRequestContext.credentials(); + } + + @Override + public boolean isTestMode() { + return gatewayRequestContext.isTestMode(); + } + + @Override + public Map userParameters() { + return gatewayRequestContext.userParameters(); + } + + @Override + public Map options() { + return gatewayRequestContext.options(); + } + + @Override + public Map httpHeaders() { + return gatewayRequestContext.httpHeaders(); + } +} diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java index 399add36c..233c3e80b 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/impl/GatewayRequestContextImpl.java @@ -15,23 +15,23 @@ */ package ai.langstream.apigateway.websocket.impl; -import ai.langstream.api.gateway.GatewayAdminRequestContext; +import ai.langstream.api.gateway.GatewayRequestContext; import ai.langstream.api.model.Application; import ai.langstream.api.model.Gateway; import java.util.Map; +import lombok.AllArgsConstructor; import lombok.Builder; @Builder -public class GatewayRequestContextImpl implements GatewayAdminRequestContext { +@AllArgsConstructor +public class GatewayRequestContextImpl implements GatewayRequestContext { private final String tenant; private final String applicationId; private final Application application; private final Gateway gateway; private final String credentials; - private final String adminCredentials; - private final String adminCredentialsType; - private final Map adminCredentialsInputs; + private final String testCredentials; private final Map userParameters; private final Map options; private final Map httpHeaders; @@ -58,14 +58,15 @@ public Gateway gateway() { @Override public String credentials() { - if (isAdminRequest()) { - return adminCredentials; + if (isTestMode()) { + return testCredentials; } return credentials; } - public boolean isAdminRequest() { - return adminCredentials != null; + @Override + public boolean isTestMode() { + return testCredentials != null; } @Override @@ -82,17 +83,4 @@ public Map options() { public Map httpHeaders() { return httpHeaders; } - - @Override - public String adminCredentialsType() { - if (isAdminRequest()) { - return adminCredentialsType; - } - throw new UnsupportedOperationException(); - } - - @Override - public Map adminCredentialsInputs() { - return isAdminRequest() ? adminCredentialsInputs : Map.of(); - } } diff --git a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/TestGatewayAuthenticationProvider.java b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/TestGatewayAuthenticationProvider.java index fe276c423..c3782954b 100644 --- a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/TestGatewayAuthenticationProvider.java +++ b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/TestGatewayAuthenticationProvider.java @@ -37,7 +37,7 @@ public GatewayAuthenticationResult authenticate(GatewayRequestContext context) { log.info("Authenticating {}", context.credentials()); if (context.credentials().startsWith("test-user-password")) { return GatewayAuthenticationResult.authenticationSuccessful( - Map.of("user-id", context.credentials())); + Map.of("login", context.credentials())); } else { return GatewayAuthenticationResult.authenticationFailed("Invalid credentials"); } diff --git a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java index 4c45f67f1..a888886f7 100644 --- a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java +++ b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java @@ -36,7 +36,7 @@ import ai.langstream.api.runtime.ClusterRuntimeRegistry; import ai.langstream.api.runtime.PluginsRegistry; import ai.langstream.api.storage.ApplicationStore; -import ai.langstream.apigateway.config.GatewayAdminAuthenticationProperties; +import ai.langstream.apigateway.config.GatewayTestAuthenticationProperties; import ai.langstream.apigateway.websocket.api.ConsumePushMessage; import ai.langstream.apigateway.websocket.api.ProduceRequest; import ai.langstream.apigateway.websocket.api.ProduceResponse; @@ -121,20 +121,18 @@ public ApplicationStore store() { @Bean @Primary - public GatewayAdminAuthenticationProperties gatewayAdminAuthenticationProperties() { - final GatewayAdminAuthenticationProperties props = - new GatewayAdminAuthenticationProperties(); - props.setTypes(List.of("http")); + public GatewayTestAuthenticationProperties gatewayTestAuthenticationProperties() { + final GatewayTestAuthenticationProperties props = + new GatewayTestAuthenticationProperties(); + props.setType("http"); props.setConfiguration( Map.of( - "http", - Map.of( - "base-url", - wireMockBaseUrl, - "path-template", - "/auth/{tenant}", - "headers", - Map.of("h1", "v1")))); + "base-url", + wireMockBaseUrl, + "path-template", + "/auth/{tenant}", + "headers", + Map.of("h1", "v1"))); return props; } } @@ -466,7 +464,7 @@ void testAuthentication() { List.of( Gateway.KeyValueComparison .valueFromAuthentication( - "header1", "user-id"))), + "header1", "login"))), null), new Gateway( "consume", @@ -481,7 +479,7 @@ void testAuthentication() { Gateway.KeyValueComparison .valueFromAuthentication( "header1", - "user-id"))))))); + "login"))))))); connectAndExpectClose( URI.create( @@ -547,7 +545,7 @@ void testAuthentication() { } @Test - void testAdminAuthentication() { + void testTestCredentials() { wireMock.register( WireMock.get("/auth/tenant1") .withHeader("Authorization", WireMock.equalTo("Bearer test-user-password")) @@ -571,7 +569,7 @@ void testAdminAuthentication() { List.of( Gateway.KeyValueComparison .valueFromAuthentication( - "header1", "user-id"))), + "header1", "login"))), null), new Gateway( "consume", @@ -586,9 +584,9 @@ void testAdminAuthentication() { Gateway.KeyValueComparison .valueFromAuthentication( "header1", - "user-id"))))), + "login"))))), new Gateway( - "consume-no-admin", + "consume-no-test", Gateway.GatewayType.consume, topic, new Gateway.Authentication("test-auth", Map.of(), false), @@ -600,13 +598,13 @@ void testAdminAuthentication() { final ClientSession client1 = connectAndCollectMessages( URI.create( - "ws://localhost:%d/v1/consume/tenant1/application1/consume?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + "ws://localhost:%d/v1/consume/tenant1/application1/consume?test-credentials=test-user-password" .formatted(port)), user1Messages); connectAndProduce( URI.create( - "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + "ws://localhost:%d/v1/produce/tenant1/application1/produce?test-credentials=test-user-password" .formatted(port)), new ProduceRequest(null, "hello user", null)); @@ -618,31 +616,24 @@ void testAdminAuthentication() { new MsgRecord( null, "hello user", - Map.of("header1", "mock-user"))), + Map.of( + "header1", + "9d75ff199d33e051209b59702de27d1e470eafb58ac6d8865788bf23b48e6818"))), user1Messages)); connectAndExpectClose( URI.create( - "ws://localhost:%d/v1/consume/tenant1/application1/consume-no-admin?admin-credentials=test-user-password&admin-credentials-type=http&admin-credentials-input-user-id=mock-user" + "ws://localhost:%d/v1/consume/tenant1/application1/consume-no-admin?test-credentials=test-user-password" .formatted(port)), new CloseReason( CloseReason.CloseCodes.VIOLATED_POLICY, - "Gateway consume-no-admin of tenant tenant1 does not allow admin requests.")); - - // use default admin auth provider - connectAndProduce( - URI.create( - "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password&admin-credentials-input-user-id=mock-user" - .formatted(port)), - new ProduceRequest(null, "hello user", null)); + "Gateway consume-no-test of tenant tenant1 does not allow test mode.")); connectAndExpectClose( URI.create( - "ws://localhost:%d/v1/produce/tenant1/application1/produce?admin-credentials=test-user-password-but-wrong&admin-credentials-input-user-id=mock-user" + "ws://localhost:%d/v1/produce/tenant1/application1/produce?test-credentials=test-user-password-but-wrong" .formatted(port)), - new CloseReason( - CloseReason.CloseCodes.VIOLATED_POLICY, - "Gateway produce of tenant tenant1 does not allow admin requests.")); + new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Invalid credentials")); } private record MsgRecord(Object key, Object value, Map headers) {} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java deleted file mode 100644 index 6432d470e..000000000 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminAuthenticationProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ai.langstream.api.gateway; - -import java.util.Map; - -public interface GatewayAdminAuthenticationProvider { - - String type(); - - void initialize(Map configuration); - - GatewayAuthenticationResult authenticate(GatewayAdminRequestContext context); -} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java deleted file mode 100644 index d3d551d4f..000000000 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAdminRequestContext.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ai.langstream.api.gateway; - -import java.util.Map; - -public interface GatewayAdminRequestContext extends GatewayRequestContext { - - String adminCredentialsType(); - - Map adminCredentialsInputs(); -} diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java index cb7866bde..f34784f89 100644 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayAuthenticationProviderRegistry.java @@ -42,26 +42,4 @@ public static GatewayAuthenticationProvider loadProvider( store.initialize(configuration); return store; } - - public static GatewayAdminAuthenticationProvider loadAdminProvider( - String type, Map configuration) { - Objects.requireNonNull(type, "type cannot be null"); - if (configuration == null) { - configuration = Map.of(); - } - ServiceLoader loader = - ServiceLoader.load(GatewayAdminAuthenticationProvider.class); - final GatewayAdminAuthenticationProvider store = - loader.stream() - .filter(p -> type.equals(p.get().type())) - .findFirst() - .orElseThrow( - () -> - new RuntimeException( - "No GatewayAdminAuthenticationProvider found for type " - + type)) - .get(); - store.initialize(configuration); - return store; - } } diff --git a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayRequestContext.java b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayRequestContext.java index e25a8451e..c70f70ff0 100644 --- a/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayRequestContext.java +++ b/langstream-api/src/main/java/ai/langstream/api/gateway/GatewayRequestContext.java @@ -31,6 +31,8 @@ public interface GatewayRequestContext { String credentials(); + boolean isTestMode(); + Map userParameters(); Map options(); diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index dd631d6d5..40bb655ee 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -58,9 +58,9 @@ public Gateway( public record Authentication( String provider, Map configuration, - @JsonProperty("allow-admin-requests") boolean allowAdminRequests) { + @JsonProperty("allow-test-mode") boolean allowTestMode) { public Authentication(String provider, Map configuration) { - this(provider, configuration, false); + this(provider, configuration, true); } } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java index f8509aa6a..47d5ab79f 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java @@ -89,37 +89,17 @@ protected String validateGatewayAndGetUrl( Map params, Map options, String credentials, - String adminCredentials, - String adminCredentialsType, - Map adminCredentialsInputs) { + String testCredentials) { validateGateway( - applicationId, - gatewayId, - type, - params, - options, - credentials, - adminCredentials, - adminCredentialsType, - adminCredentialsInputs); + applicationId, gatewayId, type, params, options, credentials, testCredentials); Map systemParams = new HashMap<>(); if (credentials != null) { systemParams.put("credentials", credentials); } - if (adminCredentials != null) { - systemParams.put("admin-credentials", adminCredentials); - } - if (adminCredentialsType != null) { - systemParams.put("admin-credentials-type", adminCredentialsType); + if (testCredentials != null) { + systemParams.put("test-credentials", testCredentials); } - if (adminCredentialsInputs != null) { - for (Map.Entry adminInput : adminCredentialsInputs.entrySet()) { - systemParams.put( - "admin-credentials-input-" + adminInput.getKey(), adminInput.getValue()); - } - } - return String.format( "%s/v1/%s/%s/%s/%s?%s", getApiGatewayUrl(), @@ -148,9 +128,7 @@ protected void validateGateway( Map params, Map options, String credentials, - String adminCredentials, - String adminCredentialsType, - Map adminCredentialsInputs) { + String testCredentials) { final AdminClient client = getClient(); @@ -194,20 +172,20 @@ protected void validateGateway( } } if (selectedGateway.getAuthentication() != null) { - if (credentials == null && adminCredentials == null) { + if (credentials == null && testCredentials == null) { throw new IllegalArgumentException( "gateway " + gatewayId + " of type " + type + " requires credentials"); } - if (adminCredentials != null) { - final Object allowAdminRequests = - selectedGateway.getAuthentication().get("allow-admin-requests"); - if (allowAdminRequests != null && allowAdminRequests.toString().equals("false")) { + if (testCredentials != null) { + final Object allowTestMode = + selectedGateway.getAuthentication().get("allow-test-mode"); + if (allowTestMode != null && allowTestMode.toString().equals("false")) { throw new IllegalArgumentException( "gateway " + gatewayId + " of type " + type - + " do not allow admin requests"); + + " do not allow test mode."); } } } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java index 60b7c6518..23585b350 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ChatGatewayCmd.java @@ -62,26 +62,16 @@ public class ChatGatewayCmd extends BaseGatewayCmd { "Credentials for the gateway. Required if the gateway requires authentication.") private String credentials; + @CommandLine.Option( + names = {"-tc", "--test-credentials"}, + description = "Test credentials for the gateway.") + private String testCredentials; + @CommandLine.Option( names = {"--connect-timeout"}, description = "Connect timeout for WebSocket connections in seconds.") private long connectTimeoutSeconds = 0; - @CommandLine.Option( - names = {"-ac", "--admin-credentials"}, - description = "Admin credentials for the gateway.") - private String adminCredentials; - - @CommandLine.Option( - names = {"-act", "--admin-credentials-type"}, - description = "Admin credentials type for the gateway.") - private String adminCredentialsType; - - @CommandLine.Option( - names = {"-aci", "--admin-credentials-input"}, - description = "Admin credentials type for the gateway.") - private Map adminCredentialsInputs; - @Override @SneakyThrows public void run() { @@ -94,9 +84,7 @@ public void run() { params, consumeGatewayOptions, credentials, - adminCredentials, - adminCredentialsType, - adminCredentialsInputs); + testCredentials); final String producePath = validateGatewayAndGetUrl( applicationId, @@ -105,9 +93,7 @@ public void run() { params, Map.of(), credentials, - adminCredentials, - adminCredentialsType, - adminCredentialsInputs); + testCredentials); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java index 1e3e7b068..d09dc839f 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ConsumeGatewayCmd.java @@ -47,19 +47,9 @@ public class ConsumeGatewayCmd extends BaseGatewayCmd { private String credentials; @CommandLine.Option( - names = {"-ac", "--admin-credentials"}, - description = "Admin credentials for the gateway.") - private String adminCredentials; - - @CommandLine.Option( - names = {"-act", "--admin-credentials-type"}, - description = "Admin credentials type for the gateway.") - private String adminCredentialsType; - - @CommandLine.Option( - names = {"-aci", "--admin-credentials-input"}, - description = "Admin credentials type for the gateway.") - private Map adminCredentialsInputs; + names = {"-tc", "--test-credentials"}, + description = "Test credentials for the gateway.") + private String testCredentials; @CommandLine.Option( names = {"--position"}, @@ -93,9 +83,7 @@ public void run() { params, options, credentials, - adminCredentials, - adminCredentialsType, - adminCredentialsInputs); + testCredentials); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java index c0b33ab95..4627b2332 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/ProduceGatewayCmd.java @@ -78,19 +78,9 @@ static class ProduceRequest { private long connectTimeoutSeconds = 0; @CommandLine.Option( - names = {"-ac", "--admin-credentials"}, - description = "Admin credentials for the gateway.") - private String adminCredentials; - - @CommandLine.Option( - names = {"-act", "--admin-credentials-type"}, - description = "Admin credentials type for the gateway.") - private String adminCredentialsType; - - @CommandLine.Option( - names = {"-aci", "--admin-credentials-input"}, - description = "Admin credentials type for the gateway.") - private Map adminCredentialsInputs; + names = {"-tc", "--test-credentials"}, + description = "Test credentials for the gateway.") + private String testCredentials; @Override @SneakyThrows @@ -103,9 +93,7 @@ public void run() { params, Map.of(), credentials, - adminCredentials, - adminCredentialsType, - adminCredentialsInputs); + testCredentials); final Duration connectTimeout = connectTimeoutSeconds > 0 ? Duration.ofSeconds(connectTimeoutSeconds) : null; CountDownLatch countDownLatch = new CountDownLatch(1); diff --git a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java index 0a41b0403..cba4213ca 100644 --- a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java +++ b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java @@ -182,7 +182,7 @@ private static Gateways resolveGateways(Application instance, Map Date: Mon, 11 Sep 2023 11:06:04 +0200 Subject: [PATCH 08/12] spotless --- .../auth/impl/github/GitHubAuthenticationProvider.java | 2 +- .../ai/langstream/cli/commands/gateway/BaseGatewayCmd.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java index 8733af54f..92221d916 100644 --- a/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-github-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/github/GitHubAuthenticationProvider.java @@ -30,7 +30,7 @@ @Slf4j public class GitHubAuthenticationProvider implements GatewayAuthenticationProvider { - protected static final ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper = new ObjectMapper(); private String clientId; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java index 47d5ab79f..76e8a4ac2 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java @@ -189,5 +189,9 @@ protected void validateGateway( } } } + if (credentials != null && testCredentials != null) { + throw new IllegalArgumentException( + "credentials and test-credentials cannot be used together"); + } } } From 3dd647ba7dc8944e878d6599193996e4f50ee230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 11 Sep 2023 11:38:39 +0200 Subject: [PATCH 09/12] rename & fix example --- .../gateway-authentication/gateways.yaml | 20 +++++++++---------- ...r.java => HttpAuthenticationProvider.java} | 6 +++--- ...pAuthenticationProviderConfiguration.java} | 8 ++++---- ....api.gateway.GatewayAuthenticationProvider | 2 +- ...er.java => JwtAuthenticationProvider.java} | 6 +++--- ...tAuthenticationProviderConfiguration.java} | 2 +- ....api.gateway.GatewayAuthenticationProvider | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) rename langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/{HttpAdminAuthenticationProvider.java => HttpAuthenticationProvider.java} (93%) rename langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/{HttpAdminAuthenticationProviderConfiguration.java => HttpAuthenticationProviderConfiguration.java} (84%) rename langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/{JwtAdminAuthenticationProvider.java => JwtAuthenticationProvider.java} (91%) rename langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/{JwtAdminAuthenticationProviderConfiguration.java => JwtAuthenticationProviderConfiguration.java} (93%) diff --git a/examples/applications/gateway-authentication/gateways.yaml b/examples/applications/gateway-authentication/gateways.yaml index 6e57a538c..09c0bd9a5 100644 --- a/examples/applications/gateway-authentication/gateways.yaml +++ b/examples/applications/gateway-authentication/gateways.yaml @@ -81,19 +81,19 @@ gateways: produce-options: headers: - key: langstream-client-user-id - valueFromAuthentication: login + value-from-authentication: login - key: langstream-client-session-id value-from-parameters: sessionId - - id: consume-output-auth-github - type: consume - topic: output-topic - parameters: - - sessionId - authentication: - provider: github - configuration: - clientId: "{{ secrets.github.client-id }}" + - id: consume-output-auth-github + type: consume + topic: output-topic + parameters: + - sessionId + authentication: + provider: github + configuration: + clientId: "{{ secrets.github.client-id }}" consume-options: filters: headers: diff --git a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java similarity index 93% rename from langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java index dc9cc8bb3..0d7361b49 100644 --- a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java @@ -29,10 +29,10 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class HttpAdminAuthenticationProvider implements GatewayAuthenticationProvider { +public class HttpAuthenticationProvider implements GatewayAuthenticationProvider { private static final ObjectMapper mapper = new ObjectMapper(); - private HttpAdminAuthenticationProviderConfiguration httpConfiguration; + private HttpAuthenticationProviderConfiguration httpConfiguration; private HttpClient httpClient; @Override @@ -45,7 +45,7 @@ public String type() { public void initialize(Map configuration) { httpConfiguration = mapper.convertValue( - configuration, HttpAdminAuthenticationProviderConfiguration.class); + configuration, HttpAuthenticationProviderConfiguration.class); httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(30)) diff --git a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProviderConfiguration.java similarity index 84% rename from langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java rename to langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProviderConfiguration.java index c8c19d3bb..01e896816 100644 --- a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAdminAuthenticationProviderConfiguration.java +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProviderConfiguration.java @@ -26,16 +26,16 @@ @Data @NoArgsConstructor @AllArgsConstructor -public class HttpAdminAuthenticationProviderConfiguration { +public class HttpAuthenticationProviderConfiguration { - @JsonAlias("base-url") + @JsonAlias({"base-url", "baseurl"}) private String baseUrl; - @JsonAlias("path-template") + @JsonAlias({"path-template", "pathtemplate"}) private String pathTemplate; private Map headers = new HashMap<>(); - @JsonAlias("accepted-statuses") + @JsonAlias({"accepted-statuses", "acceptedstatuses"}) private List acceptedStatuses = List.of(200, 201); } diff --git a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider index f471bc6af..2a472f630 100644 --- a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider @@ -1 +1 @@ -ai.langstream.apigateway.auth.impl.jwt.admin.HttpAdminAuthenticationProvider \ No newline at end of file +ai.langstream.apigateway.auth.impl.jwt.admin.HttpAuthenticationProvider \ No newline at end of file diff --git a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java similarity index 91% rename from langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java index e18963293..45bbf6321 100644 --- a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java @@ -25,7 +25,7 @@ import java.util.Map; import lombok.SneakyThrows; -public class JwtAdminAuthenticationProvider implements GatewayAuthenticationProvider { +public class JwtAuthenticationProvider implements GatewayAuthenticationProvider { private static final ObjectMapper mapper = new ObjectMapper(); private AuthenticationProviderToken authenticationProviderToken; @@ -39,9 +39,9 @@ public String type() { @Override @SneakyThrows public void initialize(Map configuration) { - final JwtAdminAuthenticationProviderConfiguration tokenProperties = + final JwtAuthenticationProviderConfiguration tokenProperties = mapper.convertValue( - configuration, JwtAdminAuthenticationProviderConfiguration.class); + configuration, JwtAuthenticationProviderConfiguration.class); if (tokenProperties.adminRoles() != null) { this.adminRoles = tokenProperties.adminRoles(); diff --git a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProviderConfiguration.java similarity index 93% rename from langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java rename to langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProviderConfiguration.java index 68f25ab5a..08375fad2 100644 --- a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAdminAuthenticationProviderConfiguration.java +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProviderConfiguration.java @@ -17,7 +17,7 @@ import java.util.List; -public record JwtAdminAuthenticationProviderConfiguration( +public record JwtAuthenticationProviderConfiguration( String secretKey, String publicKey, String authClaim, diff --git a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider index 2606bcbd5..121dce9e6 100644 --- a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/resources/META-INF/services/ai.langstream.api.gateway.GatewayAuthenticationProvider @@ -1 +1 @@ -ai.langstream.apigateway.auth.impl.jwt.admin.JwtAdminAuthenticationProvider \ No newline at end of file +ai.langstream.apigateway.auth.impl.jwt.admin.JwtAuthenticationProvider \ No newline at end of file From e3d6de7eddfac89fc90deed5d925a4e1e4fd50d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 11 Sep 2023 14:35:55 +0200 Subject: [PATCH 10/12] spotless --- bin/langstream | 4 +++- docker/build.sh | 10 ++++++---- .../impl/jwt/admin/HttpAuthenticationProvider.java | 3 +-- .../auth/impl/jwt/admin/JwtAuthenticationProvider.java | 3 +-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bin/langstream b/bin/langstream index d6cd917ff..ea779fad5 100755 --- a/bin/langstream +++ b/bin/langstream @@ -37,7 +37,9 @@ if [ ! -d langstream-cli/target/cli ]; then fi popd > /dev/null LANGSTREAM_CLI_CONFIG=${LANGSTREAM_CLI_CONFIG:-"conf/cli.yaml"} -echo "Using development CLI config file $(realpath $LANGSTREAM_CLI_CONFIG). To use the global config file, set LANGSTREAM_CLI_CONFIG=\$HOME/.langstream/config" +if [ "$LANGSTREAM_CLI_CONFIG" == "conf/cli.yaml" ]; then + echo "Using development CLI config file $(realpath $LANGSTREAM_CLI_CONFIG). To use the global config file, set LANGSTREAM_CLI_CONFIG=\$HOME/.langstream/config" +fi "$ROOT_DIR/langstream-cli/target/cli/bin/langstream" --conf "$LANGSTREAM_CLI_CONFIG" "$@" diff --git a/docker/build.sh b/docker/build.sh index 8b0f6b757..4a50d5532 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -27,11 +27,13 @@ docker_platforms() { fi } +common_flags="-DskipTests -PskipPython -Dlicense.skip -Dspotless.skip" + build_docker_image() { module=$1 - ./mvnw clean install -am -DskipTests -pl $module -T 1C -PskipPython - ./mvnw package -DskipTests -Pdocker -pl $module -Ddocker.platforms="$(docker_platforms)" -PskipPython + ./mvnw install -am -pl $module -T 1C $common_flags + ./mvnw package -Pdocker -pl $module -Ddocker.platforms="$(docker_platforms)" $common_flags docker images | head -n 2 } @@ -47,9 +49,9 @@ elif [ "$only_image" == "api-gateway" ]; then build_docker_image langstream-api-gateway else # Build all artifacts - ./mvnw install -DskipTests -T 1C -Ddocker.platforms="$(docker_platforms)" -PskipPython + ./mvnw install -T 1C -Ddocker.platforms="$(docker_platforms)" $common_flags # Build docker images - ./mvnw package -DskipTests -Pdocker -Ddocker.platforms="$(docker_platforms)" -PskipPython + ./mvnw package -Pdocker -Ddocker.platforms="$(docker_platforms)" $common_flags docker images | head -n 6 fi diff --git a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java index 0d7361b49..cb484c89c 100644 --- a/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-http-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/HttpAuthenticationProvider.java @@ -44,8 +44,7 @@ public String type() { @SneakyThrows public void initialize(Map configuration) { httpConfiguration = - mapper.convertValue( - configuration, HttpAuthenticationProviderConfiguration.class); + mapper.convertValue(configuration, HttpAuthenticationProviderConfiguration.class); httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(30)) diff --git a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java index 45bbf6321..098414c13 100644 --- a/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java +++ b/langstream-api-gateway-auth/langstream-jwt-api-gateway-auth/src/main/java/ai/langstream/apigateway/auth/impl/jwt/admin/JwtAuthenticationProvider.java @@ -40,8 +40,7 @@ public String type() { @SneakyThrows public void initialize(Map configuration) { final JwtAuthenticationProviderConfiguration tokenProperties = - mapper.convertValue( - configuration, JwtAuthenticationProviderConfiguration.class); + mapper.convertValue(configuration, JwtAuthenticationProviderConfiguration.class); if (tokenProperties.adminRoles() != null) { this.adminRoles = tokenProperties.adminRoles(); From 439c1642281704313d106ebe1d87c7a4c2b25735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 11 Sep 2023 14:58:37 +0200 Subject: [PATCH 11/12] spotless --- .../websocket/AuthenticationInterceptor.java | 4 +- .../java/ai/langstream/api/model/Gateway.java | 19 +++++---- .../ApplicationPlaceholderResolver.java | 8 ++-- .../impl/parser/ModelBuilderTest.java | 39 +++++++++++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java index 406b81150..a4347b625 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java @@ -145,10 +145,10 @@ private Map authenticate(GatewayRequestContext gatewayRequestCon } result = authTestProvider.authenticate(gatewayRequestContext); } else { - final String provider = authentication.provider(); + final String provider = authentication.getProvider(); final GatewayAuthenticationProvider authProvider = GatewayAuthenticationProviderRegistry.loadProvider( - provider, authentication.configuration()); + provider, authentication.getConfiguration()); result = authProvider.authenticate(gatewayRequestContext); } if (result == null) { diff --git a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java index 40bb655ee..8772ea9bf 100644 --- a/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java +++ b/langstream-api/src/main/java/ai/langstream/api/model/Gateway.java @@ -19,6 +19,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; public record Gateway( String id, @@ -55,13 +58,15 @@ public Gateway( this(id, type, topic, authentication, parameters, produceOptions, consumeOptions, null); } - public record Authentication( - String provider, - Map configuration, - @JsonProperty("allow-test-mode") boolean allowTestMode) { - public Authentication(String provider, Map configuration) { - this(provider, configuration, true); - } + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Authentication { + private String provider; + private Map configuration; + + @JsonProperty("allow-test-mode") + private boolean allowTestMode = true; } public record KeyValueComparison( diff --git a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java index cba4213ca..51d6ba119 100644 --- a/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java +++ b/langstream-core/src/main/java/ai/langstream/impl/common/ApplicationPlaceholderResolver.java @@ -177,12 +177,12 @@ private static Gateways resolveGateways(Application instance, Map gateways = applicationInstance.getGateways().gateways(); + Assertions.assertEquals(1, gateways.size()); + final Gateway gateway = gateways.get(0); + assertEquals("gw", gateway.id()); + assertEquals("t1", gateway.topic()); + assertEquals("google", gateway.authentication().getProvider()); + assertTrue(gateway.authentication().isAllowTestMode()); + } } From 1c3ed852d4227688bfbe60c3c78dde65c0ed5c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 11 Sep 2023 15:05:58 +0200 Subject: [PATCH 12/12] spotless --- examples/applications/gateway-authentication/gateways.yaml | 2 ++ .../apigateway/websocket/AuthenticationInterceptor.java | 2 +- .../websocket/handlers/ProduceConsumeHandlerTest.java | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/applications/gateway-authentication/gateways.yaml b/examples/applications/gateway-authentication/gateways.yaml index 09c0bd9a5..5fc5729e8 100644 --- a/examples/applications/gateway-authentication/gateways.yaml +++ b/examples/applications/gateway-authentication/gateways.yaml @@ -43,6 +43,7 @@ gateways: - sessionId authentication: provider: google + allow-test-mode: true configuration: clientId: "{{ secrets.google.client-id }}" produce-options: @@ -58,6 +59,7 @@ gateways: parameters: - sessionId authentication: + allow-test-mode: true provider: google configuration: clientId: "{{ secrets.google.client-id }}" diff --git a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java index a4347b625..e8e4594e7 100644 --- a/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java +++ b/langstream-api-gateway/src/main/java/ai/langstream/apigateway/websocket/AuthenticationInterceptor.java @@ -132,7 +132,7 @@ private Map authenticate(GatewayRequestContext gatewayRequestCon final GatewayAuthenticationResult result; if (gatewayRequestContext.isTestMode()) { - if (!authentication.allowTestMode()) { + if (!authentication.isAllowTestMode()) { throw new AuthFailedException( "Gateway " + gatewayRequestContext.gateway().id() diff --git a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java index a888886f7..8fd302237 100644 --- a/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java +++ b/langstream-api-gateway/src/test/java/ai/langstream/apigateway/websocket/handlers/ProduceConsumeHandlerTest.java @@ -458,7 +458,7 @@ void testAuthentication() { "produce", Gateway.GatewayType.produce, topic, - new Gateway.Authentication("test-auth", Map.of()), + new Gateway.Authentication("test-auth", Map.of(), true), List.of(), new Gateway.ProduceOptions( List.of( @@ -470,7 +470,7 @@ void testAuthentication() { "consume", Gateway.GatewayType.consume, topic, - new Gateway.Authentication("test-auth", Map.of()), + new Gateway.Authentication("test-auth", Map.of(), true), List.of(), null, new Gateway.ConsumeOptions(