|
1 | 1 | package org.lowcoder.plugin.graphql;
|
2 | 2 |
|
3 | 3 | import static com.google.common.base.MoreObjects.firstNonNull;
|
| 4 | +import static org.apache.commons.collections4.MapUtils.emptyIfNull; |
4 | 5 | import static org.apache.commons.lang3.StringUtils.firstNonBlank;
|
5 | 6 | import static org.apache.commons.lang3.StringUtils.trimToEmpty;
|
6 | 7 | import static org.lowcoder.plugin.graphql.GraphQLError.GRAPHQL_EXECUTION_ERROR;
|
|
10 | 11 | import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_ERROR;
|
11 | 12 | import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_TIMEOUT;
|
12 | 13 | import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.DIGEST_AUTH;
|
| 14 | +import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN; |
| 15 | +import static org.lowcoder.sdk.util.ExceptionUtils.propagateError; |
13 | 16 | import static org.lowcoder.sdk.util.JsonUtils.readTree;
|
14 | 17 | import static org.lowcoder.sdk.util.JsonUtils.toJsonThrows;
|
15 | 18 | import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheString;
|
|
30 | 33 |
|
31 | 34 | import javax.annotation.Nullable;
|
32 | 35 |
|
| 36 | +import com.google.common.collect.ImmutableMap; |
| 37 | +import org.apache.commons.collections4.CollectionUtils; |
33 | 38 | import org.apache.commons.lang3.ObjectUtils;
|
34 | 39 | import org.apache.commons.lang3.StringUtils;
|
35 | 40 | import org.lowcoder.plugin.graphql.constants.ResponseDataType;
|
|
83 | 88 | public class GraphQLExecutor implements QueryExecutor<GraphQLDatasourceConfig, Object, GraphQLQueryExecutionContext> {
|
84 | 89 | private static final String RESPONSE_DATA_TYPE = "X-LOWCODER-RESPONSE-DATA-TYPE";
|
85 | 90 | private static final String GRAPHQL_TYPE = "application/graphql";
|
| 91 | + |
| 92 | + private static final String DEFAULT_GRAPHQL_ERROR_CODE = "GRAPHQL_EXECUTION_ERROR"; |
86 | 93 | private static final int MAX_REDIRECTS = 5;
|
87 | 94 | private static final Set<String> BINARY_DATA_TYPES = Set.of("application/zip",
|
88 | 95 | "application/octet-stream",
|
@@ -245,53 +252,54 @@ private List<Property> buildBodyParams(List<Property> datasourceBodyFormData, Li
|
245 | 252 |
|
246 | 253 | @Override
|
247 | 254 | public Mono<QueryExecutionResult> executeQuery(Object o, GraphQLQueryExecutionContext context) {
|
248 |
| - return Mono.defer(() -> { |
249 |
| - URI uri = RestApiUriBuilder.buildUri(context.getUrl(), new HashMap<>(), context.getUrlParams()); |
250 |
| - WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() |
251 |
| - .disallowedHosts(commonConfig.getDisallowedHosts()) |
252 |
| - .toWebClientBuilder(); |
253 |
| - |
254 |
| - Map<String, String> allHeaders = context.getHeaders(); |
255 |
| - String contentType = context.getContentType(); |
256 |
| - allHeaders.forEach(webClientBuilder::defaultHeader); |
257 |
| - |
258 |
| - //basic auth |
259 |
| - AuthConfig authConfig = context.getAuthConfig(); |
260 |
| - if (authConfig != null && authConfig.getType() == RestApiAuthType.BASIC_AUTH) { |
261 |
| - webClientBuilder.defaultHeaders(AuthHelper.basicAuth((BasicAuthConfig) authConfig)); |
262 |
| - } |
| 255 | + return Mono.defer(() -> authByOauth2InheritFromLogin(context)) |
| 256 | + .then(Mono.defer(() -> { |
| 257 | + URI uri = RestApiUriBuilder.buildUri(context.getUrl(), new HashMap<>(), context.getUrlParams()); |
| 258 | + WebClient.Builder webClientBuilder = WebClientBuildHelper.builder() |
| 259 | + .disallowedHosts(commonConfig.getDisallowedHosts()) |
| 260 | + .toWebClientBuilder(); |
| 261 | + |
| 262 | + Map<String, String> allHeaders = context.getHeaders(); |
| 263 | + String contentType = context.getContentType(); |
| 264 | + allHeaders.forEach(webClientBuilder::defaultHeader); |
| 265 | + |
| 266 | + //basic auth |
| 267 | + AuthConfig authConfig = context.getAuthConfig(); |
| 268 | + if (authConfig != null && authConfig.getType() == RestApiAuthType.BASIC_AUTH) { |
| 269 | + webClientBuilder.defaultHeaders(AuthHelper.basicAuth((BasicAuthConfig) authConfig)); |
| 270 | + } |
263 | 271 |
|
264 |
| - if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)) { |
265 |
| - webClientBuilder.filter(new BufferingFilter()); |
266 |
| - } |
| 272 | + if (MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType)) { |
| 273 | + webClientBuilder.filter(new BufferingFilter()); |
| 274 | + } |
267 | 275 |
|
268 |
| - webClientBuilder.defaultCookies(injectCookies(context)); |
| 276 | + webClientBuilder.defaultCookies(injectCookies(context)); |
269 | 277 |
|
270 |
| - WebClient client = webClientBuilder |
271 |
| - .exchangeStrategies(EXCHANGE_STRATEGIES) |
272 |
| - .build(); |
273 |
| - if (!GRAPHQL_TYPE.equalsIgnoreCase(contentType)) { |
274 |
| - context.setQueryBody(convertToGraphQLBody(context)); |
275 |
| - } |
276 |
| - BodyInserter<?, ? super ClientHttpRequest> bodyInserter = buildBodyInserter( |
277 |
| - context.isEncodeParams(), |
278 |
| - contentType, |
279 |
| - context.getQueryBody(), |
280 |
| - context.getBodyParams()); |
281 |
| - return httpCall(client, context.getHttpMethod(), uri, bodyInserter, 0, authConfig, DEFAULT_HEADERS_CONSUMER) |
282 |
| - .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) |
283 |
| - .map(this::convertToQueryExecutionResult) |
284 |
| - .onErrorResume(error -> { |
285 |
| - if (error instanceof TimeoutException) { |
286 |
| - return Mono.just(QueryExecutionResult.error(QUERY_EXECUTION_TIMEOUT, "QUERY_TIMEOUT_ERROR", error)); |
287 |
| - } |
288 |
| - if (error instanceof PluginException pluginException) { |
289 |
| - throw pluginException; |
290 |
| - } |
291 |
| - return Mono.just( |
292 |
| - QueryExecutionResult.error(GRAPHQL_EXECUTION_ERROR, "GRAPHQL_EXECUTION_ERROR", error)); |
293 |
| - }); |
294 |
| - }); |
| 278 | + WebClient client = webClientBuilder |
| 279 | + .exchangeStrategies(EXCHANGE_STRATEGIES) |
| 280 | + .build(); |
| 281 | + if (!GRAPHQL_TYPE.equalsIgnoreCase(contentType)) { |
| 282 | + context.setQueryBody(convertToGraphQLBody(context)); |
| 283 | + } |
| 284 | + BodyInserter<?, ? super ClientHttpRequest> bodyInserter = buildBodyInserter( |
| 285 | + context.isEncodeParams(), |
| 286 | + contentType, |
| 287 | + context.getQueryBody(), |
| 288 | + context.getBodyParams()); |
| 289 | + return httpCall(client, context.getHttpMethod(), uri, bodyInserter, 0, authConfig, DEFAULT_HEADERS_CONSUMER) |
| 290 | + .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) |
| 291 | + .map(this::convertToQueryExecutionResult) |
| 292 | + .onErrorResume(error -> { |
| 293 | + if (error instanceof TimeoutException) { |
| 294 | + return Mono.just(QueryExecutionResult.error(QUERY_EXECUTION_TIMEOUT, "QUERY_TIMEOUT_ERROR", error)); |
| 295 | + } |
| 296 | + if (error instanceof PluginException pluginException) { |
| 297 | + throw pluginException; |
| 298 | + } |
| 299 | + return Mono.just( |
| 300 | + QueryExecutionResult.error(GRAPHQL_EXECUTION_ERROR, "GRAPHQL_EXECUTION_ERROR", error)); |
| 301 | + }); |
| 302 | + })); |
295 | 303 | }
|
296 | 304 |
|
297 | 305 | private Consumer<MultiValueMap<String, String>> injectCookies(GraphQLQueryExecutionContext request) {
|
@@ -458,6 +466,39 @@ private ResponseBodyData parseResponseDataInfo(byte[] body, MediaType contentTyp
|
458 | 466 | }
|
459 | 467 | }
|
460 | 468 |
|
| 469 | + private Mono<Void> authByOauth2InheritFromLogin(GraphQLQueryExecutionContext context) { |
| 470 | + if (context.getAuthConfig() == null || context.getAuthConfig().getType() != OAUTH2_INHERIT_FROM_LOGIN) { |
| 471 | + return Mono.empty(); |
| 472 | + } |
| 473 | + return context.getAuthTokenMono() |
| 474 | + .doOnNext(properties -> { |
| 475 | + Map<String, List<Property>> propertyMap = properties.stream() |
| 476 | + .collect(Collectors.groupingBy(Property::getType)); |
| 477 | + |
| 478 | + List<Property> params = propertyMap.get("param"); |
| 479 | + if (CollectionUtils.isNotEmpty(params)) { |
| 480 | + Map<String, String> paramMap = new HashMap<>(emptyIfNull(context.getUrlParams())); |
| 481 | + for (Property param : params) { |
| 482 | + paramMap.put(param.getKey(), param.getValue()); |
| 483 | + } |
| 484 | + context.setUrlParams(ImmutableMap.copyOf(paramMap)); |
| 485 | + } |
| 486 | + |
| 487 | + List<Property> headers = propertyMap.get("header"); |
| 488 | + if (CollectionUtils.isNotEmpty(headers)) { |
| 489 | + Map<String, String> headerMap = new HashMap<>(emptyIfNull(context.getHeaders())); |
| 490 | + for (Property header : headers) { |
| 491 | + headerMap.put(header.getKey(), header.getValue()); |
| 492 | + } |
| 493 | + context.setHeaders(ImmutableMap.copyOf(headerMap)); |
| 494 | + } |
| 495 | + }) |
| 496 | + .switchIfEmpty(Mono.error(new PluginException(GRAPHQL_EXECUTION_ERROR, DEFAULT_GRAPHQL_ERROR_CODE, |
| 497 | + "$ACCESS_TOKEN parameter missing."))) |
| 498 | + .onErrorResume(throwable -> propagateError(GRAPHQL_EXECUTION_ERROR, DEFAULT_GRAPHQL_ERROR_CODE, throwable)) |
| 499 | + .then(); |
| 500 | + } |
| 501 | + |
461 | 502 | @Getter
|
462 | 503 | @Builder
|
463 | 504 | private static class ResponseBodyData {
|
|
0 commit comments