From 6ed2520075c54f9111c7ab24f8fdd7728556b3eb Mon Sep 17 00:00:00 2001 From: Abdul Qadir Date: Tue, 2 Jan 2024 18:56:22 +0500 Subject: [PATCH] Add oauth(inherit from login) handling for graphql datasource --- .../plugin/graphql/GraphQLExecutor.java | 129 ++++++++++++------ .../graphql/GraphQLDatasourceConfig.java | 7 + .../api/query/ApplicationQueryApiService.java | 5 + .../api/query/LibraryQueryApiService.java | 9 +- 4 files changed, 104 insertions(+), 46 deletions(-) diff --git a/server/api-service/lowcoder-plugins/graphqlPlugin/src/main/java/org/lowcoder/plugin/graphql/GraphQLExecutor.java b/server/api-service/lowcoder-plugins/graphqlPlugin/src/main/java/org/lowcoder/plugin/graphql/GraphQLExecutor.java index f2c90b49e..cc18bf0aa 100644 --- a/server/api-service/lowcoder-plugins/graphqlPlugin/src/main/java/org/lowcoder/plugin/graphql/GraphQLExecutor.java +++ b/server/api-service/lowcoder-plugins/graphqlPlugin/src/main/java/org/lowcoder/plugin/graphql/GraphQLExecutor.java @@ -1,6 +1,7 @@ package org.lowcoder.plugin.graphql; import static com.google.common.base.MoreObjects.firstNonNull; +import static org.apache.commons.collections4.MapUtils.emptyIfNull; import static org.apache.commons.lang3.StringUtils.firstNonBlank; import static org.apache.commons.lang3.StringUtils.trimToEmpty; import static org.lowcoder.plugin.graphql.GraphQLError.GRAPHQL_EXECUTION_ERROR; @@ -10,6 +11,8 @@ import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_ERROR; import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_TIMEOUT; import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.DIGEST_AUTH; +import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN; +import static org.lowcoder.sdk.util.ExceptionUtils.propagateError; import static org.lowcoder.sdk.util.JsonUtils.readTree; import static org.lowcoder.sdk.util.JsonUtils.toJsonThrows; import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheString; @@ -30,6 +33,8 @@ import javax.annotation.Nullable; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.lowcoder.plugin.graphql.constants.ResponseDataType; @@ -83,6 +88,8 @@ public class GraphQLExecutor implements QueryExecutor { private static final String RESPONSE_DATA_TYPE = "X-LOWCODER-RESPONSE-DATA-TYPE"; private static final String GRAPHQL_TYPE = "application/graphql"; + + private static final String DEFAULT_GRAPHQL_ERROR_CODE = "GRAPHQL_EXECUTION_ERROR"; private static final int MAX_REDIRECTS = 5; private static final Set BINARY_DATA_TYPES = Set.of("application/zip", "application/octet-stream", @@ -245,53 +252,54 @@ private List buildBodyParams(List datasourceBodyFormData, Li @Override public Mono executeQuery(Object o, GraphQLQueryExecutionContext context) { - return Mono.defer(() -> { - URI uri = RestApiUriBuilder.buildUri(context.getUrl(), new HashMap<>(), context.getUrlParams()); - WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() - .disallowedHosts(commonConfig.getDisallowedHosts()) - .toWebClientBuilder(); - - Map allHeaders = context.getHeaders(); - String contentType = context.getContentType(); - allHeaders.forEach(webClientBuilder::defaultHeader); - - //basic auth - AuthConfig authConfig = context.getAuthConfig(); - if (authConfig != null && authConfig.getType() == RestApiAuthType.BASIC_AUTH) { - webClientBuilder.defaultHeaders(AuthHelper.basicAuth((BasicAuthConfig) authConfig)); - } + return Mono.defer(() -> authByOauth2InheritFromLogin(context)) + .then(Mono.defer(() -> { + URI uri = RestApiUriBuilder.buildUri(context.getUrl(), new HashMap<>(), context.getUrlParams()); + WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() + .disallowedHosts(commonConfig.getDisallowedHosts()) + .toWebClientBuilder(); + + Map allHeaders = context.getHeaders(); + String contentType = context.getContentType(); + allHeaders.forEach(webClientBuilder::defaultHeader); + + //basic auth + AuthConfig authConfig = context.getAuthConfig(); + if (authConfig != null && authConfig.getType() == RestApiAuthType.BASIC_AUTH) { + webClientBuilder.defaultHeaders(AuthHelper.basicAuth((BasicAuthConfig) authConfig)); + } - if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)) { - webClientBuilder.filter(new BufferingFilter()); - } + if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)) { + webClientBuilder.filter(new BufferingFilter()); + } - webClientBuilder.defaultCookies(injectCookies(context)); + webClientBuilder.defaultCookies(injectCookies(context)); - WebClient client = webClientBuilder - .exchangeStrategies(EXCHANGE_STRATEGIES) - .build(); - if (!GRAPHQL_TYPE.equalsIgnoreCase(contentType)) { - context.setQueryBody(convertToGraphQLBody(context)); - } - BodyInserter bodyInserter = buildBodyInserter( - context.isEncodeParams(), - contentType, - context.getQueryBody(), - context.getBodyParams()); - return httpCall(client, context.getHttpMethod(), uri, bodyInserter, 0, authConfig, DEFAULT_HEADERS_CONSUMER) - .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) - .map(this::convertToQueryExecutionResult) - .onErrorResume(error -> { - if (error instanceof TimeoutException) { - return Mono.just(QueryExecutionResult.error(QUERY_EXECUTION_TIMEOUT, "QUERY_TIMEOUT_ERROR", error)); - } - if (error instanceof PluginException pluginException) { - throw pluginException; - } - return Mono.just( - QueryExecutionResult.error(GRAPHQL_EXECUTION_ERROR, "GRAPHQL_EXECUTION_ERROR", error)); - }); - }); + WebClient client = webClientBuilder + .exchangeStrategies(EXCHANGE_STRATEGIES) + .build(); + if (!GRAPHQL_TYPE.equalsIgnoreCase(contentType)) { + context.setQueryBody(convertToGraphQLBody(context)); + } + BodyInserter bodyInserter = buildBodyInserter( + context.isEncodeParams(), + contentType, + context.getQueryBody(), + context.getBodyParams()); + return httpCall(client, context.getHttpMethod(), uri, bodyInserter, 0, authConfig, DEFAULT_HEADERS_CONSUMER) + .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) + .map(this::convertToQueryExecutionResult) + .onErrorResume(error -> { + if (error instanceof TimeoutException) { + return Mono.just(QueryExecutionResult.error(QUERY_EXECUTION_TIMEOUT, "QUERY_TIMEOUT_ERROR", error)); + } + if (error instanceof PluginException pluginException) { + throw pluginException; + } + return Mono.just( + QueryExecutionResult.error(GRAPHQL_EXECUTION_ERROR, "GRAPHQL_EXECUTION_ERROR", error)); + }); + })); } private Consumer> injectCookies(GraphQLQueryExecutionContext request) { @@ -458,6 +466,39 @@ private ResponseBodyData parseResponseDataInfo(byte[] body, MediaType contentTyp } } + private Mono authByOauth2InheritFromLogin(GraphQLQueryExecutionContext context) { + if (context.getAuthConfig() == null || context.getAuthConfig().getType() != OAUTH2_INHERIT_FROM_LOGIN) { + return Mono.empty(); + } + return context.getAuthTokenMono() + .doOnNext(properties -> { + Map> propertyMap = properties.stream() + .collect(Collectors.groupingBy(Property::getType)); + + List params = propertyMap.get("param"); + if (CollectionUtils.isNotEmpty(params)) { + Map paramMap = new HashMap<>(emptyIfNull(context.getUrlParams())); + for (Property param : params) { + paramMap.put(param.getKey(), param.getValue()); + } + context.setUrlParams(ImmutableMap.copyOf(paramMap)); + } + + List headers = propertyMap.get("header"); + if (CollectionUtils.isNotEmpty(headers)) { + Map headerMap = new HashMap<>(emptyIfNull(context.getHeaders())); + for (Property header : headers) { + headerMap.put(header.getKey(), header.getValue()); + } + context.setHeaders(ImmutableMap.copyOf(headerMap)); + } + }) + .switchIfEmpty(Mono.error(new PluginException(GRAPHQL_EXECUTION_ERROR, DEFAULT_GRAPHQL_ERROR_CODE, + "$ACCESS_TOKEN parameter missing."))) + .onErrorResume(throwable -> propagateError(GRAPHQL_EXECUTION_ERROR, DEFAULT_GRAPHQL_ERROR_CODE, throwable)) + .then(); + } + @Getter @Builder private static class ResponseBodyData { diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/graphql/GraphQLDatasourceConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/graphql/GraphQLDatasourceConfig.java index 3c9d3392e..0099ae2fa 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/graphql/GraphQLDatasourceConfig.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/graphql/GraphQLDatasourceConfig.java @@ -110,6 +110,13 @@ public boolean isForwardAllCookies() { return forwardAllCookies; } + public boolean isOauth2InheritFromLogin() { + if (this.authConfig != null) { + return this.authConfig.getType().name().equals(RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN.name()); + } + return false; + } + @Override public DatasourceConnectionConfig mergeWithUpdatedConfig(DatasourceConnectionConfig updatedConfig) { if (!(updatedConfig instanceof GraphQLDatasourceConfig updatedApiConfig)) { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/ApplicationQueryApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/ApplicationQueryApiService.java index 669bd501d..dbf77db11 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/ApplicationQueryApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/ApplicationQueryApiService.java @@ -23,6 +23,7 @@ import org.lowcoder.sdk.exception.BizError; import org.lowcoder.sdk.models.Property; import org.lowcoder.sdk.models.QueryExecutionResult; +import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig; import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig; import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig; import org.lowcoder.sdk.query.QueryVisitorContext; @@ -122,7 +123,11 @@ public Mono executeApplicationQuery(ServerWebExchange exch if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig && restApiDatasourceConfig.isOauth2InheritFromLogin()) { paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId()); + } + if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig + && graphQLDatasourceConfig.isOauth2InheritFromLogin()) { + paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId()); } QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies, paramsAndHeadersInheritFromLogin, commonConfig.getDisallowedHosts()); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryApiService.java index c9eb022d5..663a3be5d 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryApiService.java @@ -42,6 +42,7 @@ import org.lowcoder.sdk.exception.PluginCommonError; import org.lowcoder.sdk.models.Property; import org.lowcoder.sdk.models.QueryExecutionResult; +import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig; import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig; import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig; import org.lowcoder.sdk.query.QueryVisitorContext; @@ -310,12 +311,16 @@ public Mono executeLibraryQuery(ServerWebExchange exchange // check if oauth inherited from login and save token - if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig - && restApiDatasourceConfig.isOauth2InheritFromLogin()) { + if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig && restApiDatasourceConfig.isOauth2InheritFromLogin()) { paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin (user, ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId()); } + if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig && graphQLDatasourceConfig.isOauth2InheritFromLogin()) { + paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin + (user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId()); + } + QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin, commonConfig.getDisallowedHosts()); Map queryConfig = baseQuery.getQueryConfig();