diff --git a/server/api-service/openblocks-plugins/elasticSearchPlugin/src/main/java/com/openblocks/plugin/es/EsConnector.java b/server/api-service/openblocks-plugins/elasticSearchPlugin/src/main/java/com/openblocks/plugin/es/EsConnector.java index 722a19ed..8c9b3356 100644 --- a/server/api-service/openblocks-plugins/elasticSearchPlugin/src/main/java/com/openblocks/plugin/es/EsConnector.java +++ b/server/api-service/openblocks-plugins/elasticSearchPlugin/src/main/java/com/openblocks/plugin/es/EsConnector.java @@ -34,6 +34,7 @@ import com.google.common.base.Joiner; import com.openblocks.plugin.es.model.EsConnection; import com.openblocks.plugin.es.model.EsDatasourceConfig; +import com.openblocks.sdk.config.CommonConfig; import com.openblocks.sdk.config.dynamic.Conf; import com.openblocks.sdk.config.dynamic.ConfigCenter; import com.openblocks.sdk.exception.BizError; @@ -44,7 +45,6 @@ import com.openblocks.sdk.util.ExceptionUtils; import com.openblocks.sdk.util.JsonUtils; import com.openblocks.sdk.util.Preconditions; -import com.openblocks.sdk.webclient.NameResolver; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; @@ -61,10 +61,12 @@ public class EsConnector implements DatasourceConnector datasourceValidateTimeout; + private final CommonConfig commonConfig; - public EsConnector(ConfigCenter configCenter) { + public EsConnector(ConfigCenter configCenter, CommonConfig commonConfig) { datasourceValidateTimeout = configCenter.mongoPlugin().ofInteger("datasourceValidateTimeoutMillis", 6000) .then(Duration::ofMillis); + this.commonConfig = commonConfig; } @Nonnull @@ -100,7 +102,7 @@ public Mono createConnection(EsDatasourceConfig connectionConfig) */ private RestClient buildRestClient(EsDatasourceConfig esDatasourceConfig) { ConnectionStringParseResult parseResult = parseConnectionString(esDatasourceConfig.getConnectionString()); - if (NameResolver.DISALLOWED_HOSTS.contains(parseResult.getHost())) { + if (commonConfig.getDisallowedHosts().contains(parseResult.getHost())) { throw new BizException(BizError.INVALID_DATASOURCE_CONFIG_TYPE, "INVALID_CONNECTION_STRING"); } HttpHost httpHost = new HttpHost(parseResult.getHost(), parseResult.getPort(), parseResult.getSchema()); diff --git a/server/api-service/openblocks-plugins/graphqlPlugin/src/main/java/com/openblocks/plugin/graphql/GraphQLExecutor.java b/server/api-service/openblocks-plugins/graphqlPlugin/src/main/java/com/openblocks/plugin/graphql/GraphQLExecutor.java index 952275f9..c6aba754 100644 --- a/server/api-service/openblocks-plugins/graphqlPlugin/src/main/java/com/openblocks/plugin/graphql/GraphQLExecutor.java +++ b/server/api-service/openblocks-plugins/graphqlPlugin/src/main/java/com/openblocks/plugin/graphql/GraphQLExecutor.java @@ -13,8 +13,6 @@ import static com.openblocks.sdk.util.MustacheHelper.renderMustacheString; import static com.openblocks.sdk.util.StreamUtils.collectList; import static com.openblocks.sdk.util.StreamUtils.distinctByKey; -import static com.openblocks.sdk.webclient.WebClients.builder; -import static com.openblocks.sdk.webclient.WebClients.withSafeHost; import static org.apache.commons.lang3.StringUtils.firstNonBlank; import static org.apache.commons.lang3.StringUtils.trimToEmpty; @@ -61,6 +59,7 @@ import com.openblocks.plugin.graphql.helpers.BufferingFilter; import com.openblocks.plugin.graphql.model.GraphQLQueryConfig; import com.openblocks.plugin.graphql.model.GraphQLQueryExecutionContext; +import com.openblocks.sdk.config.CommonConfig; import com.openblocks.sdk.exception.PluginException; import com.openblocks.sdk.models.Property; import com.openblocks.sdk.models.QueryExecutionResult; @@ -76,6 +75,7 @@ import com.openblocks.sdk.util.JsonUtils; import com.openblocks.sdk.util.MoreMapUtils; import com.openblocks.sdk.util.MustacheHelper; +import com.openblocks.sdk.webclient.WebClientBuildHelper; import lombok.Builder; import lombok.Getter; @@ -100,6 +100,12 @@ public class GraphQLExecutor implements QueryExecutor DEFAULT_HEADERS_CONSUMER = httpHeaders -> {}; + private final CommonConfig commonConfig; + + public GraphQLExecutor(CommonConfig commonConfig) { + this.commonConfig = commonConfig; + } + private static List renderMustacheValueInProperties(List properties, Map paramMap) { return properties.stream() .map(it -> { @@ -244,7 +250,9 @@ private List buildBodyParams(List datasourceBodyFormData, Li public Mono executeQuery(Object o, GraphQLQueryExecutionContext context) { return Mono.defer(() -> { URI uri = RestApiUriBuilder.buildUri(context.getUrl(), new HashMap<>(), context.getUrlParams()); - WebClient.Builder webClientBuilder = withSafeHost(builder()); + WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() + .disallowedHosts(commonConfig.getDisallowedHosts()) + .toWebClientBuilder(); Map allHeaders = context.getHeaders(); String contentType = context.getContentType(); diff --git a/server/api-service/openblocks-plugins/restApiPlugin/src/main/java/com/openblocks/plugin/restapi/RestApiExecutor.java b/server/api-service/openblocks-plugins/restApiPlugin/src/main/java/com/openblocks/plugin/restapi/RestApiExecutor.java index 65306508..b5e415ac 100644 --- a/server/api-service/openblocks-plugins/restApiPlugin/src/main/java/com/openblocks/plugin/restapi/RestApiExecutor.java +++ b/server/api-service/openblocks-plugins/restApiPlugin/src/main/java/com/openblocks/plugin/restapi/RestApiExecutor.java @@ -41,7 +41,6 @@ import static com.openblocks.sdk.util.StreamUtils.collectList; import static org.apache.commons.collections4.MapUtils.emptyIfNull; import static org.apache.commons.lang3.StringUtils.trimToEmpty; -import static org.springframework.web.reactive.function.client.WebClient.builder; import java.io.IOException; import java.net.URI; @@ -103,7 +102,7 @@ import com.openblocks.sdk.plugin.restapi.auth.BasicAuthConfig; import com.openblocks.sdk.plugin.restapi.auth.RestApiAuthType; import com.openblocks.sdk.query.QueryVisitorContext; -import com.openblocks.sdk.webclient.WebClients; +import com.openblocks.sdk.webclient.WebClientBuildHelper; import lombok.Builder; import lombok.Getter; @@ -238,7 +237,10 @@ public Mono executeQuery(Object webClientFilter, RestApiQu return Mono.defer(() -> authByOauth2InheritFromLogin(context)) .then(Mono.defer(() -> { - WebClient.Builder webClientBuilder = WebClients.withSafeHostAndSecure(builder(), context.getSslConfig()); + WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() + .disallowedHosts(commonConfig.getDisallowedHosts()) + .sslConfig(context.getSslConfig()) + .toWebClientBuilder(); Map allHeaders = context.getHeaders(); String contentType = context.getContentType(); diff --git a/server/api-service/openblocks-plugins/restApiPlugin/src/test/java/com/openblocks/plugin/restapi/RestApiEngineTest.java b/server/api-service/openblocks-plugins/restApiPlugin/src/test/java/com/openblocks/plugin/restapi/RestApiEngineTest.java index bf2b8164..7012c087 100644 --- a/server/api-service/openblocks-plugins/restApiPlugin/src/test/java/com/openblocks/plugin/restapi/RestApiEngineTest.java +++ b/server/api-service/openblocks-plugins/restApiPlugin/src/test/java/com/openblocks/plugin/restapi/RestApiEngineTest.java @@ -38,7 +38,7 @@ public class RestApiEngineTest { private static final RestApiConnector connector = new RestApiConnector(); private final QueryVisitorContext queryVisitorContext = new QueryVisitorContext("userId1", - "workspace1", 8080, null, null); + "workspace1", 8080, null, null, null); @Test public void testUrlConcatenationWithUriBuilder() { diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java index aa5f8399..4425ec6d 100644 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java @@ -3,7 +3,9 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -34,6 +36,7 @@ public class CommonConfig { private int maxQueryResponseSizeInMb = 10; private Cookie cookie = new Cookie(); private JsExecutor jsExecutor = new JsExecutor(); + private Set disallowedHosts = new HashSet<>(); public boolean isSelfHost() { return !isCloud(); diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/query/QueryVisitorContext.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/query/QueryVisitorContext.java index 19a44fc0..bdc8b52a 100644 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/query/QueryVisitorContext.java +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/query/QueryVisitorContext.java @@ -1,6 +1,7 @@ package com.openblocks.sdk.query; import java.util.List; +import java.util.Set; import org.springframework.http.HttpCookie; import org.springframework.util.MultiValueMap; @@ -20,13 +21,15 @@ public class QueryVisitorContext { private final int systemPort; private final Mono> authTokenMono; + private final Set disallowedHosts; public QueryVisitorContext(String visitorId, String applicationOrgId, int systemPort, - MultiValueMap cookies, Mono> authTokenMono) { + MultiValueMap cookies, Mono> authTokenMono, Set disallowedHosts) { this.visitorId = visitorId; this.applicationOrgId = applicationOrgId; this.systemPort = systemPort; this.cookies = cookies; this.authTokenMono = authTokenMono; + this.disallowedHosts = disallowedHosts; } } diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/HttpClients.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/HttpClients.java deleted file mode 100644 index 3473694f..00000000 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/HttpClients.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.openblocks.sdk.webclient; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLException; - -import com.openblocks.sdk.plugin.common.ssl.DisableVerifySslConfig; -import com.openblocks.sdk.plugin.common.ssl.SslConfig; -import com.openblocks.sdk.plugin.common.ssl.SslHelper; -import com.openblocks.sdk.plugin.common.ssl.VerifySelfSignedCertSslConfig; - -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import lombok.extern.slf4j.Slf4j; -import reactor.netty.http.client.HttpClient; -import reactor.netty.tcp.SslProvider; - -@Slf4j -public class HttpClients { - - public static HttpClient httpClient() { - return HttpClient.create(); - } - - public static HttpClient withSafeHost(HttpClient httpClient) { - return httpClient.resolver(ResolverGroup.INSTANCE); - } - - public static HttpClient withSecure(HttpClient httpClient, SslConfig sslConfig) { - if (sslConfig instanceof DisableVerifySslConfig) { - return httpClient.secure(disableSslCertificateVerificationWebClientBuilder()); - } - if (sslConfig instanceof VerifySelfSignedCertSslConfig verifySelfSignedCertSslConfig) { - return httpClient.secure(getSelfSignedCertWebClient(verifySelfSignedCertSslConfig)); - } - return httpClient; - } - - private static SslProvider getSelfSignedCertWebClient(VerifySelfSignedCertSslConfig verifySelfSignedCertSslConfig) { - try { - X509Certificate x509Certificate = SslHelper.parseCertificate(verifySelfSignedCertSslConfig.getSelfSignedCert()); - SslContext sslContext = SslContextBuilder.forClient() - .trustManager(x509Certificate) - .build(); - return SslProvider.builder() - .sslContext(sslContext) - .build(); - } catch (CertificateException | SSLException e) { - log.error("parse certificate error", e); - return SslProvider.defaultClientProvider(); - } - } - - private static SslProvider disableSslCertificateVerificationWebClientBuilder() { - try { - SslContext sslContext = SslContextBuilder.forClient() - .trustManager(InsecureTrustManagerFactory.INSTANCE) - .build(); - return SslProvider.builder() - .sslContext(sslContext) - .build(); - } catch (SSLException e) { - return SslProvider.defaultClientProvider(); - } - } - -} diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/NameResolver.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/NameResolver.java deleted file mode 100644 index 8f82953b..00000000 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/NameResolver.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.openblocks.sdk.webclient; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -import io.netty.resolver.InetNameResolver; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Promise; -import io.netty.util.internal.SocketUtils; - -public class NameResolver extends InetNameResolver { - - public static final Set DISALLOWED_HOSTS = Set.of( - "169.254.169.254", - "metadata.google.internal", - "localhost", - "127.0.0.1" - ); - - public NameResolver(EventExecutor executor) { - super(executor); - } - - @Override - protected void doResolve(String inetHost, Promise promise) { - if (DISALLOWED_HOSTS.contains(inetHost)) { - promise.setFailure(new UnknownHostException("Host not allowed.")); - return; - } - - final InetAddress address; - try { - address = SocketUtils.addressByName(inetHost); - } catch (UnknownHostException e) { - promise.setFailure(e); - return; - } - - if (DISALLOWED_HOSTS.contains(address.getHostAddress())) { - promise.setFailure(new UnknownHostException("Host not allowed.")); - return; - } - - promise.setSuccess(address); - } - - @Override - protected void doResolveAll(String inetHost, Promise> promise) { - if (DISALLOWED_HOSTS.contains(inetHost)) { - promise.setFailure(new UnknownHostException("Host not allowed.")); - return; - } - - final List addresses; - try { - addresses = Arrays.asList(SocketUtils.allAddressesByName(inetHost)); - } catch (UnknownHostException e) { - promise.setFailure(e); - return; - } - - // Even if _one_ of the addresses is disallowed, we fail the request. - for (InetAddress address : addresses) { - if (DISALLOWED_HOSTS.contains(address.getHostAddress())) { - promise.setFailure(new UnknownHostException("Host not allowed.")); - return; - } - } - - promise.setSuccess(addresses); - } -} \ No newline at end of file diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/ResolverGroup.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/ResolverGroup.java deleted file mode 100644 index 406c33a6..00000000 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/ResolverGroup.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.openblocks.sdk.webclient; - -import java.net.InetSocketAddress; - -import io.netty.resolver.AddressResolver; -import io.netty.resolver.AddressResolverGroup; -import io.netty.resolver.InetSocketAddressResolver; -import io.netty.util.concurrent.EventExecutor; - -public class ResolverGroup extends AddressResolverGroup { - - public static final ResolverGroup INSTANCE = new ResolverGroup(); - - @Override - protected AddressResolver newResolver(EventExecutor executor) { - return new InetSocketAddressResolver(executor, new NameResolver(executor)); - } -} \ No newline at end of file diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/SafeHostResolverGroup.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/SafeHostResolverGroup.java new file mode 100644 index 00000000..e63c4e9c --- /dev/null +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/SafeHostResolverGroup.java @@ -0,0 +1,92 @@ +package com.openblocks.sdk.webclient; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import io.netty.resolver.AddressResolver; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.InetNameResolver; +import io.netty.resolver.InetSocketAddressResolver; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.SocketUtils; + +public class SafeHostResolverGroup extends AddressResolverGroup { + + private final Set disallowedHosts; + + public SafeHostResolverGroup(Set disallowedHosts) { + this.disallowedHosts = disallowedHosts; + } + + @Override + protected AddressResolver newResolver(EventExecutor executor) { + return new InetSocketAddressResolver(executor, new SafeHostNameResolver(executor, disallowedHosts)); + } + + /** + * Safe-host name resolver which disallow some hosts. + */ + private static class SafeHostNameResolver extends InetNameResolver { + + private final Set disallowedHosts; + + public SafeHostNameResolver(EventExecutor executor, Set disallowedHosts) { + super(executor); + this.disallowedHosts = disallowedHosts; + } + + @Override + protected void doResolve(String inetHost, Promise promise) { + if (disallowedHosts.contains(inetHost)) { + promise.setFailure(new UnknownHostException("Host not allowed.")); + return; + } + + final InetAddress address; + try { + address = SocketUtils.addressByName(inetHost); + } catch (UnknownHostException e) { + promise.setFailure(e); + return; + } + + if (disallowedHosts.contains(address.getHostAddress())) { + promise.setFailure(new UnknownHostException("Host not allowed.")); + return; + } + + promise.setSuccess(address); + } + + @Override + protected void doResolveAll(String inetHost, Promise> promise) { + if (disallowedHosts.contains(inetHost)) { + promise.setFailure(new UnknownHostException("Host not allowed.")); + return; + } + + final List addresses; + try { + addresses = Arrays.asList(SocketUtils.allAddressesByName(inetHost)); + } catch (UnknownHostException e) { + promise.setFailure(e); + return; + } + + // Even if _one_ of the addresses is disallowed, we fail the request. + for (InetAddress address : addresses) { + if (disallowedHosts.contains(address.getHostAddress())) { + promise.setFailure(new UnknownHostException("Host not allowed.")); + return; + } + } + + promise.setSuccess(addresses); + } + } +} \ No newline at end of file diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClientBuildHelper.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClientBuildHelper.java new file mode 100644 index 00000000..85afad1e --- /dev/null +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClientBuildHelper.java @@ -0,0 +1,118 @@ +package com.openblocks.sdk.webclient; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Set; + +import javax.net.ssl.SSLException; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClient.Builder; + +import com.openblocks.sdk.plugin.common.ssl.DisableVerifySslConfig; +import com.openblocks.sdk.plugin.common.ssl.SslConfig; +import com.openblocks.sdk.plugin.common.ssl.SslHelper; +import com.openblocks.sdk.plugin.common.ssl.VerifySelfSignedCertSslConfig; + +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import lombok.extern.slf4j.Slf4j; +import reactor.netty.http.client.HttpClient; +import reactor.netty.tcp.SslProvider; +import reactor.netty.transport.ProxyProvider.Proxy; + +@Slf4j +public class WebClientBuildHelper { + + private static final String proxyHost; + private static final String proxyPortStr; + + private SslConfig sslConfig; + private Set disallowedHosts; + private boolean systemProxy; + + static { + proxyHost = System.getProperty("http.proxyHost"); + proxyPortStr = System.getProperty("http.proxyPort"); + } + + private WebClientBuildHelper() { + } + + public static WebClientBuildHelper builder() { + return new WebClientBuildHelper(); + } + + public WebClientBuildHelper sslConfig(SslConfig sslConfig) { + this.sslConfig = sslConfig; + return this; + } + + public WebClientBuildHelper disallowedHosts(Set disallowedHosts) { + this.disallowedHosts = disallowedHosts; + return this; + } + + public WebClientBuildHelper systemProxy() { + this.systemProxy = true; + return this; + } + + public WebClient build() { + return toWebClientBuilder().build(); + } + + public Builder toWebClientBuilder() { + HttpClient httpClient = HttpClient.create(); + if (sslConfig != null) { + if (sslConfig instanceof DisableVerifySslConfig) { + httpClient = httpClient.secure(sslProviderWithoutCertVerify()); + } + if (sslConfig instanceof VerifySelfSignedCertSslConfig verifySelfSignedCertSslConfig) { + httpClient = httpClient.secure(sslProviderWithSelfSignedCert(verifySelfSignedCertSslConfig)); + } + } + if (systemProxy && StringUtils.isNoneBlank(proxyHost, proxyPortStr)) { + httpClient = httpClient.proxy(typeSpec -> typeSpec.type(Proxy.HTTP) + .host(proxyHost) + .port(Integer.parseInt(proxyPortStr))); + } + if (CollectionUtils.isNotEmpty(disallowedHosts)) { + httpClient = httpClient.resolver(new SafeHostResolverGroup(disallowedHosts)); + } + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)); + } + + private static SslProvider sslProviderWithSelfSignedCert(VerifySelfSignedCertSslConfig verifySelfSignedCertSslConfig) { + try { + X509Certificate x509Certificate = SslHelper.parseCertificate(verifySelfSignedCertSslConfig.getSelfSignedCert()); + SslContext sslContext = SslContextBuilder.forClient() + .trustManager(x509Certificate) + .build(); + return SslProvider.builder() + .sslContext(sslContext) + .build(); + } catch (CertificateException | SSLException e) { + log.error("parse certificate error", e); + return SslProvider.defaultClientProvider(); + } + } + + private static SslProvider sslProviderWithoutCertVerify() { + try { + SslContext sslContext = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + return SslProvider.builder() + .sslContext(sslContext) + .build(); + } catch (SSLException e) { + return SslProvider.defaultClientProvider(); + } + } +} diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClients.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClients.java index 3eb21b10..0adf0ff8 100644 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClients.java +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/webclient/WebClients.java @@ -1,44 +1,12 @@ package com.openblocks.sdk.webclient; -import static com.openblocks.sdk.webclient.HttpClients.httpClient; -import static com.openblocks.sdk.webclient.HttpClients.withSecure; - -import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClient.Builder; - -import com.openblocks.sdk.plugin.common.ssl.SslConfig; -import lombok.extern.slf4j.Slf4j; -import reactor.netty.http.client.HttpClient; - -@Slf4j public class WebClients { - private static final WebClient INSTANCE = builder().build(); - - private static final WebClient SECURE_INSTANCE = withSafeHost(builder()).build(); + private static final WebClient INSTANCE = WebClient.builder().build(); public static WebClient getInstance() { return INSTANCE; } - - public static WebClient getSecureInstance() { - return SECURE_INSTANCE; - } - - public static Builder builder() { - return WebClient.builder(); - } - - public static Builder withSafeHost(Builder builder) { - HttpClient httpClient = HttpClients.withSafeHost(httpClient()); - return builder.clientConnector(new ReactorClientHttpConnector(httpClient)); - } - - public static Builder withSafeHostAndSecure(Builder builder, SslConfig sslConfig) { - HttpClient httpClient = withSecure(HttpClients.withSafeHost(httpClient()), sslConfig); - return builder.clientConnector(new ReactorClientHttpConnector(httpClient)); - } - } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/AuthRequest.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/AuthRequest.java index c16aaa5f..83e13449 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/AuthRequest.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/AuthRequest.java @@ -1,8 +1,8 @@ package com.openblocks.api.authentication.request; import com.openblocks.domain.authentication.context.AuthRequestContext; +import com.openblocks.domain.user.model.AuthToken; import com.openblocks.domain.user.model.AuthUser; -import com.openblocks.domain.user.model.ConnectionAuthToken; import reactor.core.publisher.Mono; @@ -13,7 +13,7 @@ public interface AuthRequest { Mono auth(AuthRequestContext authRequestContext); - default Mono refresh(ConnectionAuthToken old) { + default Mono refresh(String refreshToken) { return Mono.error(new UnsupportedOperationException()); } } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java index 09e925f4..15318abe 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java @@ -26,8 +26,8 @@ public Mono build(OAuth2RequestContext context) { private AbstractOauth2Request buildRequest(OAuth2RequestContext context) { return switch (context.getAuthConfig().getAuthType()) { - case GITHUB -> new GithubRequest((Oauth2SimpleAuthConfig) context.getAuthConfig(), context); - case GOOGLE -> new GoogleRequest((Oauth2SimpleAuthConfig) context.getAuthConfig(), context); + case GITHUB -> new GithubRequest((Oauth2SimpleAuthConfig) context.getAuthConfig()); + case GOOGLE -> new GoogleRequest((Oauth2SimpleAuthConfig) context.getAuthConfig()); default -> throw new UnsupportedOperationException(context.getAuthConfig().getAuthType()); }; } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/AbstractOauth2Request.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/AbstractOauth2Request.java index edd77aa5..b1e041be 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/AbstractOauth2Request.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/AbstractOauth2Request.java @@ -1,5 +1,6 @@ package com.openblocks.api.authentication.request.oauth2.request; +import static com.openblocks.api.authentication.util.AuthenticationUtils.AUTH_REQUEST_THREAD_POOL; import static com.openblocks.sdk.exception.BizError.FAIL_TO_GET_OIDC_INFO; import static com.openblocks.sdk.util.ExceptionUtils.deferredError; import static com.openblocks.sdk.util.JsonUtils.toJson; @@ -7,11 +8,9 @@ import com.openblocks.api.authentication.request.AuthRequest; import com.openblocks.api.authentication.request.oauth2.OAuth2RequestContext; import com.openblocks.api.authentication.request.oauth2.Oauth2Source; -import com.openblocks.api.authentication.util.AuthenticationUtils; import com.openblocks.domain.authentication.context.AuthRequestContext; import com.openblocks.domain.user.model.AuthToken; import com.openblocks.domain.user.model.AuthUser; -import com.openblocks.domain.user.model.ConnectionAuthToken; import com.openblocks.sdk.auth.Oauth2SimpleAuthConfig; import lombok.extern.slf4j.Slf4j; @@ -22,46 +21,23 @@ public abstract class AbstractOauth2Request im protected T config; protected Oauth2Source source; - protected OAuth2RequestContext context; - public AbstractOauth2Request(T config, Oauth2Source source, OAuth2RequestContext context) { + public AbstractOauth2Request(T config, Oauth2Source source) { this.config = config; this.source = source; - this.context = context; } public Mono auth(AuthRequestContext authRequestContext) { - return Mono.defer(() -> { - try { - OAuth2RequestContext context = (OAuth2RequestContext) authRequestContext; - - AuthToken token = this.getAuthToken(context); - AuthUser authUser = this.getAuthUser(token); - authUser.setAuthToken(token); - return Mono.just(authUser); - } catch (Exception e) { - log.error("get oidc failed: {}", toJson(authRequestContext), e); - return deferredError(FAIL_TO_GET_OIDC_INFO, "FAIL_TO_GET_OIDC_INFO", e.getMessage()); - } + return getAuthToken((OAuth2RequestContext) authRequestContext) + .flatMap(authToken -> getAuthUser(authToken).doOnNext(authUser -> authUser.setAuthToken(authToken))) + .onErrorResume(throwable -> { + log.error("get oidc failed: {}", toJson(authRequestContext), throwable); + return deferredError(FAIL_TO_GET_OIDC_INFO, "FAIL_TO_GET_OIDC_INFO", throwable.getMessage()); }) - .subscribeOn(AuthenticationUtils.AUTH_REQUEST_THREAD_POOL); + .subscribeOn(AUTH_REQUEST_THREAD_POOL); } - protected abstract AuthToken getAuthToken(OAuth2RequestContext context); - - protected abstract AuthUser getAuthUser(AuthToken authToken); + protected abstract Mono getAuthToken(OAuth2RequestContext context); - public Mono refresh(ConnectionAuthToken old) { - return Mono.fromSupplier(() -> { - AuthToken authToken = AuthToken.builder() - .refreshToken(old.getRefreshToken()) - .build(); - AuthToken refresh = refresh(authToken); - return ConnectionAuthToken.of(refresh); - }); - } - - protected AuthToken refresh(AuthToken authToken) { - throw new UnsupportedOperationException(); - } + protected abstract Mono getAuthUser(AuthToken authToken); } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GithubRequest.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GithubRequest.java index 1f60f2db..1a7e7946 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GithubRequest.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GithubRequest.java @@ -3,51 +3,61 @@ import static java.net.URLDecoder.decode; import static java.nio.charset.StandardCharsets.UTF_8; +import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; -import org.json.JSONObject; +import org.springframework.core.ParameterizedTypeReference; +import com.openblocks.api.authentication.request.AuthException; import com.openblocks.api.authentication.request.oauth2.OAuth2RequestContext; import com.openblocks.api.authentication.request.oauth2.Oauth2DefaultSource; -import com.openblocks.sdk.util.HttpUtils; -import com.openblocks.api.authentication.request.AuthException; import com.openblocks.domain.user.model.AuthToken; import com.openblocks.domain.user.model.AuthUser; import com.openblocks.sdk.auth.Oauth2SimpleAuthConfig; +import com.openblocks.sdk.util.JsonUtils; +import com.openblocks.sdk.webclient.WebClients; + +import reactor.core.publisher.Mono; public class GithubRequest extends AbstractOauth2Request { - public GithubRequest(Oauth2SimpleAuthConfig config, OAuth2RequestContext context) { - super(config, Oauth2DefaultSource.GITHUB, context); + public GithubRequest(Oauth2SimpleAuthConfig config) { + super(config, Oauth2DefaultSource.GITHUB); } @Override - protected AuthToken getAuthToken(OAuth2RequestContext context) { - String result; - + protected Mono getAuthToken(OAuth2RequestContext context) { + URI uri; try { - result = new URIBuilder(source.accessToken()) + uri = new URIBuilder(source.accessToken()) .addParameter("code", context.getCode()) .addParameter("client_id", config.getClientId()) .addParameter("client_secret", config.getClientSecret()) .addParameter("grant_type", "authorization_code") - .addParameter("redirect_uri", GithubRequest.this.context.getRedirectUrl()) - .toString(); + .addParameter("redirect_uri", context.getRedirectUrl()) + .build(); } catch (URISyntaxException e) { throw new RuntimeException(e); } - String response = HttpUtils.post(result, null, null, null); - Map res = parseStringToMap(response); - - this.checkResponse(new JSONObject(res)); - - return AuthToken.builder() - .accessToken(res.get("access_token")) - .build(); + return WebClients.getInstance() + .post() + .uri(uri) + .exchangeToMono(response -> response.bodyToMono(String.class)) + .map(this::parseStringToMap) + .flatMap(map -> { + if (map.containsKey("error")) { + return Mono.error(new AuthException(JsonUtils.toJson(map))); + } + AuthToken accessToken = AuthToken.builder() + .accessToken(map.get("access_token")) + .build(); + return Mono.just(accessToken); + }); } private Map parseStringToMap(String s) { @@ -66,24 +76,24 @@ private Map parseStringToMap(String s) { } @Override - protected AuthUser getAuthUser(AuthToken authToken) { - String response = HttpUtils.get(source.userInfo(), null, Map.of("Authorization", "token " + authToken.getAccessToken())); - - JSONObject object = new JSONObject(response); - - this.checkResponse(object); - - return AuthUser.builder() - .uid(object.get("id").toString()) - .username(object.getString("login")) - .avatar(object.getString("avatar_url")) - .rawUserInfo(object.toMap()) - .build(); - } - - private void checkResponse(JSONObject object) { - if (object.has("error")) { - throw new AuthException(object); - } + protected Mono getAuthUser(AuthToken authToken) { + return WebClients.getInstance() + .get() + .uri(source.userInfo()) + .header("Authorization", "token " + authToken.getAccessToken()) + .exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference>() { + })) + .flatMap(map -> { + if (map.containsKey("error")) { + return Mono.error(new AuthException(JsonUtils.toJson(map))); + } + AuthUser authUser = AuthUser.builder() + .uid(map.get("id").toString()) + .username(MapUtils.getString(map, "login")) + .avatar(MapUtils.getString(map, "avatar_url")) + .rawUserInfo(map) + .build(); + return Mono.just(authUser); + }); } } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GoogleRequest.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GoogleRequest.java index 546d21bd..8054d7fb 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GoogleRequest.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/authentication/request/oauth2/request/GoogleRequest.java @@ -1,77 +1,85 @@ package com.openblocks.api.authentication.request.oauth2.request; +import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import org.apache.commons.collections4.MapUtils; import org.apache.http.client.utils.URIBuilder; -import org.json.JSONObject; +import org.springframework.core.ParameterizedTypeReference; +import com.openblocks.api.authentication.request.AuthException; import com.openblocks.api.authentication.request.oauth2.OAuth2RequestContext; import com.openblocks.api.authentication.request.oauth2.Oauth2DefaultSource; -import com.openblocks.sdk.util.HttpUtils; -import com.openblocks.api.authentication.request.AuthException; import com.openblocks.domain.user.model.AuthToken; import com.openblocks.domain.user.model.AuthUser; import com.openblocks.sdk.auth.Oauth2SimpleAuthConfig; +import com.openblocks.sdk.util.JsonUtils; +import com.openblocks.sdk.webclient.WebClientBuildHelper; + +import reactor.core.publisher.Mono; public class GoogleRequest extends AbstractOauth2Request { - public GoogleRequest(Oauth2SimpleAuthConfig config, OAuth2RequestContext context) { - super(config, Oauth2DefaultSource.GOOGLE, context); + public GoogleRequest(Oauth2SimpleAuthConfig config) { + super(config, Oauth2DefaultSource.GOOGLE); } @Override - protected AuthToken getAuthToken(OAuth2RequestContext context) { - String result; - + protected Mono getAuthToken(OAuth2RequestContext context) { + URI uri; try { - result = new URIBuilder(source.accessToken()) + uri = new URIBuilder(source.accessToken()) .addParameter("code", context.getCode()) .addParameter("client_id", config.getClientId()) .addParameter("client_secret", config.getClientSecret()) .addParameter("grant_type", "authorization_code") - .addParameter("redirect_uri", GoogleRequest.this.context.getRedirectUrl()) - .toString(); + .addParameter("redirect_uri", context.getRedirectUrl()) + .build(); } catch (URISyntaxException e) { throw new RuntimeException(e); } - String response = HttpUtils.post(result, null, null, null); - JSONObject accessTokenObject = new JSONObject(response); - this.checkResponse(accessTokenObject); - return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getInt("expires_in")) - .build(); - } - - @Override - protected AuthUser getAuthUser(AuthToken authToken) { - String userInfo = HttpUtils.post(userInfoUrl(authToken), null, Map.of("Authorization", "Bearer " + authToken.getAccessToken()), null); - - JSONObject object = new JSONObject(userInfo); - this.checkResponse(object); - return AuthUser.builder() - .uid(object.getString("sub")) - .username(object.getString("name")) - .avatar(object.getString("picture")) - .rawUserInfo(object.toMap()) - .build(); + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .post() + .uri(uri) + .exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference>() { + })) + .flatMap(map -> { + if (map.containsKey("error") || map.containsKey("error_description")) { + throw new AuthException(JsonUtils.toJson(map)); + } + AuthToken authToken = AuthToken.builder() + .accessToken(MapUtils.getString(map, "access_token")) + .expireIn(MapUtils.getIntValue(map, "expires_in")) + .build(); + return Mono.just(authToken); + }); } - private String userInfoUrl(AuthToken authToken) { - try { - return new URIBuilder(source.userInfo()) - .addParameter("access_token", authToken.getAccessToken()) - .toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - private void checkResponse(JSONObject object) { - if (object.has("error") || object.has("error_description")) { - throw new AuthException(object); - } + @Override + protected Mono getAuthUser(AuthToken authToken) { + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .post() + .uri(source.userInfo()) + .header("Authorization", "Bearer " + authToken.getAccessToken()) + .exchangeToMono(response -> response.bodyToMono(new ParameterizedTypeReference>() { + })) + .flatMap(map -> { + if (map.containsKey("error") || map.containsKey("error_description")) { + throw new AuthException(JsonUtils.toJson(map)); + } + AuthUser authUser = AuthUser.builder() + .uid(MapUtils.getString(map, "sub")) + .username(MapUtils.getString(map, "name")) + .avatar(MapUtils.getString(map, "picture")) + .rawUserInfo(map) + .build(); + return Mono.just(authUser); + }); } } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/ApplicationQueryApiService.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/ApplicationQueryApiService.java index fca333f6..84ca10eb 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/ApplicationQueryApiService.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/ApplicationQueryApiService.java @@ -35,6 +35,7 @@ import com.openblocks.domain.query.service.LibraryQueryService; import com.openblocks.domain.query.service.QueryExecutionService; import com.openblocks.infra.util.TupleUtils; +import com.openblocks.sdk.config.CommonConfig; import com.openblocks.sdk.exception.BizError; import com.openblocks.sdk.models.Property; import com.openblocks.sdk.models.QueryExecutionResult; @@ -68,6 +69,9 @@ public class ApplicationQueryApiService { @Autowired private QueryExecutionService queryExecutionService; + @Autowired + private CommonConfig commonConfig; + @Value("${server.port}") private int port; @@ -107,7 +111,7 @@ public Mono executeApplicationQuery(ServerWebExchange exch MultiValueMap cookies = exchange.getRequest().getCookies(); QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies, - getAuthParamsAndHeadersInheritFromLogin(userId, app.getOrganizationId())); + getAuthParamsAndHeadersInheritFromLogin(userId, app.getOrganizationId()), commonConfig.getDisallowedHosts()); return queryExecutionService.executeQuery(datasource, baseQuery.getQueryConfig(), queryExecutionRequest.paramMap(), appQuery.getTimeoutStr(), queryVisitorContext ) diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/LibraryQueryApiService.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/LibraryQueryApiService.java index f9270c8c..6375b75f 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/LibraryQueryApiService.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/query/LibraryQueryApiService.java @@ -44,6 +44,7 @@ import com.openblocks.domain.query.service.QueryExecutionService; import com.openblocks.domain.user.model.User; import com.openblocks.domain.user.service.UserService; +import com.openblocks.sdk.config.CommonConfig; import com.openblocks.sdk.exception.BizError; import com.openblocks.sdk.exception.PluginCommonError; import com.openblocks.sdk.models.Property; @@ -84,6 +85,9 @@ public class LibraryQueryApiService { @Autowired private ResourcePermissionService resourcePermissionService; + @Autowired + private CommonConfig commonConfig; + @Value("${server.port}") private int port; @@ -254,8 +258,8 @@ public Mono executeLibraryQueryFromJs(ServerWebExchange ex QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, exchange.getRequest().getCookies(), - paramsAndHeadersInheritFromLogin - ); + paramsAndHeadersInheritFromLogin, + commonConfig.getDisallowedHosts()); Map queryConfig = baseQuery.getQueryConfig(); String timeoutStr = firstNonBlank(baseQuery.getTimeoutStr(), "5s"); @@ -292,7 +296,8 @@ public Mono executeLibraryQuery(ServerWebExchange exchange Datasource datasource = tuple.getT3(); Mono> paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin(userId, orgId); - QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin); + QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin, + commonConfig.getDisallowedHosts()); Map queryConfig = baseQuery.getQueryConfig(); String timeoutStr = baseQuery.getTimeoutStr(); return queryExecutionService.executeQuery(datasource, queryConfig, queryExecutionRequest.paramMap(), timeoutStr,