Skip to content

Commit 113c768

Browse files
authored
Merge pull request #624 from lowcoder-org/add-oauth-handling-for-open-api-datasources
Add Oauth Handling For Open API Datasources
2 parents 6002540 + e1d2094 commit 113c768

File tree

8 files changed

+105
-15
lines changed

8 files changed

+105
-15
lines changed

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/query/service/QueryExecutionService.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.lowcoder.sdk.config.CommonConfig;
1111
import org.lowcoder.sdk.exception.BizException;
1212
import org.lowcoder.sdk.exception.PluginException;
13+
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
14+
import org.lowcoder.sdk.models.Property;
1315
import org.lowcoder.sdk.models.QueryExecutionResult;
1416
import org.lowcoder.sdk.query.QueryExecutionContext;
1517
import org.lowcoder.sdk.query.QueryVisitorContext;
@@ -18,6 +20,7 @@
1820
import reactor.core.publisher.Mono;
1921

2022
import java.time.Duration;
23+
import java.util.HashMap;
2124
import java.util.List;
2225
import java.util.Map;
2326
import java.util.concurrent.TimeoutException;
@@ -94,6 +97,26 @@ private Mono<QueryExecutionResult> executeByNodeJs(Datasource datasource, Map<St
9497
.collect(Collectors.toList());
9598
context.addAll(cookies);
9699

97-
return datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig());
100+
// forward oauth2 access token in case of oauth2(inherit from login)
101+
102+
if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
103+
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
104+
return Mono.defer(() -> injectOauth2Token(queryVisitorContext, context))
105+
.then(Mono.defer(() -> datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig())));
106+
} else {
107+
return datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig());
108+
}
109+
110+
111+
}
112+
113+
private Mono<Void> injectOauth2Token(QueryVisitorContext queryVisitorContext, List<Map<String, Object>> context) {
114+
return queryVisitorContext.getAuthTokenMono()
115+
.doOnNext(properties -> {
116+
for (Property property : properties) {
117+
context.add(Map.of("key" , property.getKey(), "value", property.getValue()));
118+
}
119+
})
120+
.then();
98121
}
99122
}

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/models/JsDatasourceConnectionConfig.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.apache.commons.collections4.MapUtils;
1818
import org.apache.commons.lang3.ObjectUtils;
19+
import org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType;
1920
import org.springframework.data.annotation.Transient;
2021

2122
import lombok.Getter;
@@ -159,6 +160,20 @@ public DatasourceConnectionConfig mergeWithUpdatedConfig(DatasourceConnectionCon
159160
if (this.containsKey("extra") || jsDatasourceConnectionConfig.containsKey("extra")) {
160161
newJsDatasourceConnectionConfig.putIfAbsent("extra", ObjectUtils.firstNonNull(jsDatasourceConnectionConfig.getExtra(), this.getExtra()));
161162
}
163+
164+
// for oauth handling
165+
if(this.containsKey("authConfig")) {
166+
if(jsDatasourceConnectionConfig.containsKey("authConfig")) {
167+
newJsDatasourceConnectionConfig.put("authConfig", jsDatasourceConnectionConfig.get("authConfig"));
168+
} else {
169+
// do nothing, save empty ( this will clear db )
170+
}
171+
} else {
172+
if(jsDatasourceConnectionConfig.containsKey("authConfig")) {
173+
newJsDatasourceConnectionConfig.put("authConfig", jsDatasourceConnectionConfig.get("authConfig"));
174+
}
175+
}
176+
162177
return newJsDatasourceConnectionConfig;
163178
}
164179

@@ -199,4 +214,18 @@ private DatasourceConnectionConfig doEncryptOrDecrypt(Function<String, String> e
199214

200215
return this;
201216
}
217+
218+
public boolean isOauth2InheritFromLogin() {
219+
if (this.get("authConfig") != null) {
220+
return ((HashMap<String, String>)this.get("authConfig")).get("type").equals(RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN.name());
221+
}
222+
return false;
223+
}
224+
225+
public String getAuthId() {
226+
if(isOauth2InheritFromLogin()) {
227+
return ((HashMap<String, String>)this.get("authConfig")).get("authId");
228+
}
229+
return null;
230+
}
202231
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.lowcoder.infra.util.TupleUtils;
2222
import org.lowcoder.sdk.config.CommonConfig;
2323
import org.lowcoder.sdk.exception.BizError;
24+
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
2425
import org.lowcoder.sdk.models.Property;
2526
import org.lowcoder.sdk.models.QueryExecutionResult;
2627
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
@@ -122,12 +123,18 @@ public Mono<QueryExecutionResult> executeApplicationQuery(ServerWebExchange exch
122123
// Check if oauth inherited from login and save token
123124
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig
124125
&& restApiDatasourceConfig.isOauth2InheritFromLogin()) {
125-
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
126+
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId(), false);
126127
}
127128

128129
if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig
129130
&& graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
130-
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
131+
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId(), false);
132+
}
133+
134+
135+
if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
136+
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
137+
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), jsDatasourceConnectionConfig.getAuthId(), true);
131138
}
132139

133140
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies, paramsAndHeadersInheritFromLogin, commonConfig.getDisallowedHosts());
@@ -196,7 +203,7 @@ private Mono<BaseQuery> getBaseQueryFromLibraryQuery(ApplicationQuery query) {
196203
.map(LibraryQueryRecord::getQuery);
197204
}
198205

199-
protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user, String authId) {
206+
protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user, String authId, boolean isJsQuery) {
200207
if(authId == null) {
201208
return Mono.empty();
202209
}
@@ -207,7 +214,11 @@ protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user
207214
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
208215
return Mono.empty();
209216
}
210-
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
217+
if(isJsQuery) {
218+
return Mono.just(Collections.singletonList(new Property("OAUTH_ACCESS_TOKEN",activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
219+
} else {
220+
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
221+
}
211222
}
212223

213224
protected void onNextOrError(QueryExecutionRequest queryExecutionRequest, QueryVisitorContext queryVisitorContext,

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.lowcoder.sdk.config.CommonConfig;
4141
import org.lowcoder.sdk.exception.BizError;
4242
import org.lowcoder.sdk.exception.PluginCommonError;
43+
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
4344
import org.lowcoder.sdk.models.Property;
4445
import org.lowcoder.sdk.models.QueryExecutionResult;
4546
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
@@ -263,7 +264,7 @@ public Mono<QueryExecutionResult> executeLibraryQueryFromJs(ServerWebExchange ex
263264
Datasource datasource = tuple.getT3();
264265
User user = tuple.getT4();
265266
Mono<List<Property>> paramsAndHeadersInheritFromLogin = orgMember.isInvalid()
266-
? Mono.empty() : getParamsAndHeadersInheritFromLogin(user, null);
267+
? Mono.empty() : getParamsAndHeadersInheritFromLogin(user, null, false);
267268

268269
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port,
269270
exchange.getRequest().getCookies(),
@@ -313,12 +314,18 @@ public Mono<QueryExecutionResult> executeLibraryQuery(ServerWebExchange exchange
313314
// check if oauth inherited from login and save token
314315
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig && restApiDatasourceConfig.isOauth2InheritFromLogin()) {
315316
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
316-
(user, ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
317+
(user, ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId(), false);
317318
}
318319

319320
if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig && graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
320321
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
321-
(user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
322+
(user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId(), false);
323+
}
324+
325+
if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
326+
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
327+
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
328+
(user, jsDatasourceConnectionConfig.getAuthId(), true);
322329
}
323330

324331
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin,
@@ -348,7 +355,7 @@ private Mono<BaseQuery> getBaseQuery(LibraryQueryCombineId libraryQueryCombineId
348355
.map(LibraryQueryRecord::getQuery);
349356
}
350357

351-
protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, String authId) {
358+
protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, String authId, boolean isJsQuery) {
352359
if(authId == null) {
353360
return Mono.empty();
354361
}
@@ -359,7 +366,11 @@ protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, St
359366
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
360367
return Mono.empty();
361368
}
362-
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
369+
if(isJsQuery) {
370+
return Mono.just(Collections.singletonList(new Property("OAUTH_ACCESS_TOKEN",activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
371+
} else {
372+
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
373+
}
363374
}
364375

365376
protected void onNextOrError(QueryExecutionRequest queryExecutionRequest, QueryVisitorContext queryVisitorContext, BaseQuery baseQuery,

server/node-service/src/plugins/openApi/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export async function runOpenApi(
102102

103103
try {
104104
const { parameters, requestBody } = normalizeParams(otherActionData, operation, isOas3Spec);
105-
const securities = extractSecurityParams(dataSourceConfig.dynamicParamsConfig, definition);
105+
let securities = extractSecurityParams(dataSourceConfig, definition);
106106
const response = await SwaggerClient.execute({
107107
spec: definition,
108108
operationId: realOperationId,

server/node-service/src/plugins/openApi/util.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { extractSecurityParams, getSchemaExample, extractLevelData, parseUrl } from "./util";
22

33
test("extractSecurityParams", () => {
4-
const params = extractSecurityParams({ "ApiKeyAuth.value": "hello", ApiKeyAuth: null }, {
4+
const params = extractSecurityParams({"dynamicParamsConfig":{ "ApiKeyAuth.value": "hello", ApiKeyAuth: null }}, {
55
openapi: "3.0",
66
components: {
77
securitySchemes: {

server/node-service/src/plugins/openApi/util.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ interface NormalizedParams {
8181
requestBody?: any;
8282
}
8383

84-
export function extractSecurityParams(config: any, spec: OpenAPI.Document) {
84+
export function extractSecurityParams(datasourceConfig: any, spec: OpenAPI.Document) {
85+
const config = datasourceConfig.dynamicParamsConfig;
8586
if (!config) {
8687
return {};
8788
}
@@ -96,6 +97,18 @@ export function extractSecurityParams(config: any, spec: OpenAPI.Document) {
9697
names = Object.keys(swagger2Spec.securityDefinitions || {});
9798
}
9899
const authorized = _.pick(authData, names);
100+
101+
let oauthAccessToken = datasourceConfig["OAUTH_ACCESS_TOKEN"];
102+
103+
if(oauthAccessToken) {
104+
return {
105+
authorized: {
106+
OAUTH_ACCESS_TOKEN: { value: oauthAccessToken }
107+
},
108+
specSecurity: []
109+
};
110+
}
111+
99112
return { authorized, specSecurity: spec.security };
100113
}
101114

server/node-service/src/services/plugin.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,12 @@ export async function runPluginQuery(
212212
const queryConfig = await getQueryConfig(plugin, dataSourceConfig);
213213
const action = await evalToValue(queryConfig, dsl, context, dataSourceConfig);
214214

215-
//forward cookies
215+
// forward cookies
216216
context.forEach(({ key, value }) => {
217-
if (dataSourceConfig.dynamicParamsConfig && key in dataSourceConfig.dynamicParamsConfig) {
217+
// for oauth(inherit from login) support
218+
if(key == "OAUTH_ACCESS_TOKEN") {
219+
dataSourceConfig["OAUTH_ACCESS_TOKEN"] = value
220+
} else if (dataSourceConfig.dynamicParamsConfig && key in dataSourceConfig.dynamicParamsConfig) {
218221
const valueKey = `${key}.value`;
219222
dataSourceConfig.dynamicParamsConfig[valueKey] = value[0].value
220223
}

0 commit comments

Comments
 (0)