Skip to content

Commit 38b992b

Browse files
Merge branch 'dev' into fix-oauth-provider-attributes-mapping
2 parents 818cb5d + 83d254e commit 38b992b

File tree

10 files changed

+138
-48
lines changed

10 files changed

+138
-48
lines changed

‎.DS_Store

0 Bytes
Binary file not shown.

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ client/node_modules/
99
client/packages/lowcoder-plugin-demo/.yarn/install-state.gz
1010
client/packages/lowcoder-plugin-demo/yarn.lock
1111
client/packages/lowcoder-plugin-demo/.yarn/cache/@types-node-npm-16.18.68-56f72825c0-094ae9ed80.zip
12+
.DS_Store

‎client/packages/lowcoder/src/api/datasourceApi.ts

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export interface HttpConfig {
102102

103103
authConfig: {
104104
type: AuthType;
105+
authId?: string;
105106
} & (
106107
| {
107108
username: string; // basic auth

‎client/packages/lowcoder/src/constants/userConstants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const UserConnectionSource = {
1111
};
1212

1313
export type UserConnection = {
14+
authId: any;
1415
source: string;
1516
name: string;
1617
rawUserInfo?: JSONObject;

‎client/packages/lowcoder/src/pages/datasource/form/httpDatasourceForm.tsx

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Rule } from "antd/lib/form";
2-
import { HttpConfig } from "api/datasourceApi";
2+
import { HttpConfig, OAuthConfig } from "api/datasourceApi";
33
import {
44
DatasourceForm,
55
FormInputItem,
@@ -21,12 +21,14 @@ import {
2121
} from "../form";
2222
import { DatasourceFormProps } from "./datasourceFormRegistry";
2323
import { useHostCheck } from "./useHostCheck";
24+
import { useSelector } from "react-redux";
25+
import { getUser } from "redux/selectors/usersSelectors";
2426

2527
const AuthTypeOptions = [
2628
{ label: "None", value: "NO_AUTH" },
2729
{ label: "Basic", value: "BASIC_AUTH" },
2830
{ label: "Digest", value: "DIGEST_AUTH" },
29-
{ label: "OAuth 2.0(Inherit from login)", value: "OAUTH2_INHERIT_FROM_LOGIN" },
31+
{ label: "OAuth 2.0 (Inherit from login)", value: "OAUTH2_INHERIT_FROM_LOGIN" },
3032
// { label: "OAuth 2.0", value: "oAuth2" },
3133
] as const;
3234

@@ -50,7 +52,16 @@ export const HttpDatasourceForm = (props: DatasourceFormProps) => {
5052
const datasourceConfig = datasource?.datasourceConfig as HttpConfig;
5153
// const oauthConfig = datasourceConfig?.authConfig as OAuthConfig;
5254

55+
// here we get the Auth Sources from a user to enable user impersonation
56+
const userAuthSources = useSelector(getUser).connections?.filter(authSource => authSource.source !== "EMAIL");;
57+
const userAuthSourcesOptions = userAuthSources?.map(item => ({
58+
label: item.source,
59+
value: item.authId
60+
})) || [];
61+
5362
const [authType, setAuthType] = useState(datasourceConfig?.authConfig?.type);
63+
const [authId, setAuthId] = useState(datasourceConfig?.authConfig?.authId);
64+
5465
// const [grantType, setGrantType] = useState(oauthConfig?.grantType ?? "authorization_code");
5566

5667
const hostRule = useHostCheck();
@@ -98,6 +109,22 @@ export const HttpDatasourceForm = (props: DatasourceFormProps) => {
98109
}
99110
};
100111

112+
const showUserAuthSourceSelector = () => {
113+
if (authType === "OAUTH2_INHERIT_FROM_LOGIN") {
114+
return (
115+
<FormSelectItem
116+
name={"authId"}
117+
label="User Authentication Source"
118+
options={userAuthSourcesOptions}
119+
initialValue={datasourceConfig?.authConfig?authId : null}
120+
afterChange={(value) => setAuthId(value) }
121+
labelWidth={142}
122+
/>
123+
);
124+
}
125+
return null;
126+
};
127+
101128
return (
102129
<DatasourceForm form={form} preserve={false}>
103130
<FormSection size={props.size}>
@@ -149,6 +176,7 @@ export const HttpDatasourceForm = (props: DatasourceFormProps) => {
149176
afterChange={(value) => setAuthType(value)}
150177
labelWidth={142}
151178
/>
179+
{showUserAuthSourceSelector()}
152180
{showAuthItem(authType)}
153181
</FormSection>
154182

‎client/packages/lowcoder/src/pages/datasource/form/useDatasourceForm.ts

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function useDatasourceForm() {
5050
...config,
5151
authConfig: {
5252
type: form.getFieldsValue()["authConfigType"],
53+
authId: form.getFieldsValue()["authId"],
5354
username: form.getFieldsValue()["username"],
5455
password: form.getFieldsValue()["password"],
5556
},

‎server/api-service/lowcoder-plugins/graphqlPlugin/src/main/java/org/lowcoder/plugin/graphql/GraphQLExecutor.java

+85-44
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.plugin.graphql;
22

33
import static com.google.common.base.MoreObjects.firstNonNull;
4+
import static org.apache.commons.collections4.MapUtils.emptyIfNull;
45
import static org.apache.commons.lang3.StringUtils.firstNonBlank;
56
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
67
import static org.lowcoder.plugin.graphql.GraphQLError.GRAPHQL_EXECUTION_ERROR;
@@ -10,6 +11,8 @@
1011
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_ERROR;
1112
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_TIMEOUT;
1213
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;
1316
import static org.lowcoder.sdk.util.JsonUtils.readTree;
1417
import static org.lowcoder.sdk.util.JsonUtils.toJsonThrows;
1518
import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheString;
@@ -30,6 +33,8 @@
3033

3134
import javax.annotation.Nullable;
3235

36+
import com.google.common.collect.ImmutableMap;
37+
import org.apache.commons.collections4.CollectionUtils;
3338
import org.apache.commons.lang3.ObjectUtils;
3439
import org.apache.commons.lang3.StringUtils;
3540
import org.lowcoder.plugin.graphql.constants.ResponseDataType;
@@ -83,6 +88,8 @@
8388
public class GraphQLExecutor implements QueryExecutor<GraphQLDatasourceConfig, Object, GraphQLQueryExecutionContext> {
8489
private static final String RESPONSE_DATA_TYPE = "X-LOWCODER-RESPONSE-DATA-TYPE";
8590
private static final String GRAPHQL_TYPE = "application/graphql";
91+
92+
private static final String DEFAULT_GRAPHQL_ERROR_CODE = "GRAPHQL_EXECUTION_ERROR";
8693
private static final int MAX_REDIRECTS = 5;
8794
private static final Set<String> BINARY_DATA_TYPES = Set.of("application/zip",
8895
"application/octet-stream",
@@ -245,53 +252,54 @@ private List<Property> buildBodyParams(List<Property> datasourceBodyFormData, Li
245252

246253
@Override
247254
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+
}
263271

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+
}
267275

268-
webClientBuilder.defaultCookies(injectCookies(context));
276+
webClientBuilder.defaultCookies(injectCookies(context));
269277

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+
}));
295303
}
296304

297305
private Consumer<MultiValueMap<String, String>> injectCookies(GraphQLQueryExecutionContext request) {
@@ -458,6 +466,39 @@ private ResponseBodyData parseResponseDataInfo(byte[] body, MediaType contentTyp
458466
}
459467
}
460468

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+
461502
@Getter
462503
@Builder
463504
private static class ResponseBodyData {

‎server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/graphql/GraphQLDatasourceConfig.java

+7
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ public boolean isForwardAllCookies() {
110110
return forwardAllCookies;
111111
}
112112

113+
public boolean isOauth2InheritFromLogin() {
114+
if (this.authConfig != null) {
115+
return this.authConfig.getType().name().equals(RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN.name());
116+
}
117+
return false;
118+
}
119+
113120
@Override
114121
public DatasourceConnectionConfig mergeWithUpdatedConfig(DatasourceConnectionConfig updatedConfig) {
115122
if (!(updatedConfig instanceof GraphQLDatasourceConfig updatedApiConfig)) {

‎server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/ApplicationQueryApiService.java

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.lowcoder.sdk.exception.BizError;
2424
import org.lowcoder.sdk.models.Property;
2525
import org.lowcoder.sdk.models.QueryExecutionResult;
26+
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
2627
import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig;
2728
import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig;
2829
import org.lowcoder.sdk.query.QueryVisitorContext;
@@ -122,7 +123,11 @@ public Mono<QueryExecutionResult> executeApplicationQuery(ServerWebExchange exch
122123
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig
123124
&& restApiDatasourceConfig.isOauth2InheritFromLogin()) {
124125
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
126+
}
125127

128+
if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig
129+
&& graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
130+
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
126131
}
127132

128133
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies, paramsAndHeadersInheritFromLogin, commonConfig.getDisallowedHosts());

‎server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryApiService.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.lowcoder.sdk.exception.PluginCommonError;
4343
import org.lowcoder.sdk.models.Property;
4444
import org.lowcoder.sdk.models.QueryExecutionResult;
45+
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
4546
import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig;
4647
import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig;
4748
import org.lowcoder.sdk.query.QueryVisitorContext;
@@ -310,12 +311,16 @@ public Mono<QueryExecutionResult> executeLibraryQuery(ServerWebExchange exchange
310311

311312

312313
// check if oauth inherited from login and save token
313-
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig
314-
&& restApiDatasourceConfig.isOauth2InheritFromLogin()) {
314+
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig && restApiDatasourceConfig.isOauth2InheritFromLogin()) {
315315
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
316316
(user, ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
317317
}
318318

319+
if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig && graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
320+
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
321+
(user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
322+
}
323+
319324
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin,
320325
commonConfig.getDisallowedHosts());
321326
Map<String, Object> queryConfig = baseQuery.getQueryConfig();

0 commit comments

Comments
 (0)