Skip to content

Commit

Permalink
Merge pull request #624 from lowcoder-org/add-oauth-handling-for-open…
Browse files Browse the repository at this point in the history
…-api-datasources

Add Oauth Handling For Open API Datasources
  • Loading branch information
FalkWolsky authored Jan 11, 2024
2 parents 6002540 + e1d2094 commit 113c768
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.lowcoder.sdk.config.CommonConfig;
import org.lowcoder.sdk.exception.BizException;
import org.lowcoder.sdk.exception.PluginException;
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
import org.lowcoder.sdk.models.Property;
import org.lowcoder.sdk.models.QueryExecutionResult;
import org.lowcoder.sdk.query.QueryExecutionContext;
import org.lowcoder.sdk.query.QueryVisitorContext;
Expand All @@ -18,6 +20,7 @@
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
Expand Down Expand Up @@ -94,6 +97,26 @@ private Mono<QueryExecutionResult> executeByNodeJs(Datasource datasource, Map<St
.collect(Collectors.toList());
context.addAll(cookies);

return datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig());
// forward oauth2 access token in case of oauth2(inherit from login)

if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
return Mono.defer(() -> injectOauth2Token(queryVisitorContext, context))
.then(Mono.defer(() -> datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig())));
} else {
return datasourcePluginClient.executeQuery(datasource.getType(), queryConfig, context, datasource.getDetailConfig());
}


}

private Mono<Void> injectOauth2Token(QueryVisitorContext queryVisitorContext, List<Map<String, Object>> context) {
return queryVisitorContext.getAuthTokenMono()
.doOnNext(properties -> {
for (Property property : properties) {
context.add(Map.of("key" , property.getKey(), "value", property.getValue()));
}
})
.then();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType;
import org.springframework.data.annotation.Transient;

import lombok.Getter;
Expand Down Expand Up @@ -159,6 +160,20 @@ public DatasourceConnectionConfig mergeWithUpdatedConfig(DatasourceConnectionCon
if (this.containsKey("extra") || jsDatasourceConnectionConfig.containsKey("extra")) {
newJsDatasourceConnectionConfig.putIfAbsent("extra", ObjectUtils.firstNonNull(jsDatasourceConnectionConfig.getExtra(), this.getExtra()));
}

// for oauth handling
if(this.containsKey("authConfig")) {
if(jsDatasourceConnectionConfig.containsKey("authConfig")) {
newJsDatasourceConnectionConfig.put("authConfig", jsDatasourceConnectionConfig.get("authConfig"));
} else {
// do nothing, save empty ( this will clear db )
}
} else {
if(jsDatasourceConnectionConfig.containsKey("authConfig")) {
newJsDatasourceConnectionConfig.put("authConfig", jsDatasourceConnectionConfig.get("authConfig"));
}
}

return newJsDatasourceConnectionConfig;
}

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

return this;
}

public boolean isOauth2InheritFromLogin() {
if (this.get("authConfig") != null) {
return ((HashMap<String, String>)this.get("authConfig")).get("type").equals(RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN.name());
}
return false;
}

public String getAuthId() {
if(isOauth2InheritFromLogin()) {
return ((HashMap<String, String>)this.get("authConfig")).get("authId");
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.lowcoder.infra.util.TupleUtils;
import org.lowcoder.sdk.config.CommonConfig;
import org.lowcoder.sdk.exception.BizError;
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
import org.lowcoder.sdk.models.Property;
import org.lowcoder.sdk.models.QueryExecutionResult;
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
Expand Down Expand Up @@ -122,12 +123,18 @@ public Mono<QueryExecutionResult> executeApplicationQuery(ServerWebExchange exch
// Check if oauth inherited from login and save token
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig
&& restApiDatasourceConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId(), false);
}

if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig
&& graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId(), false);
}


if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getAuthParamsAndHeadersInheritFromLogin(tuple.getT1(), jsDatasourceConnectionConfig.getAuthId(), true);
}

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

protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user, String authId) {
protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user, String authId, boolean isJsQuery) {
if(authId == null) {
return Mono.empty();
}
Expand All @@ -207,7 +214,11 @@ protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
return Mono.empty();
}
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
if(isJsQuery) {
return Mono.just(Collections.singletonList(new Property("OAUTH_ACCESS_TOKEN",activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
} else {
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
}
}

protected void onNextOrError(QueryExecutionRequest queryExecutionRequest, QueryVisitorContext queryVisitorContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.lowcoder.sdk.config.CommonConfig;
import org.lowcoder.sdk.exception.BizError;
import org.lowcoder.sdk.exception.PluginCommonError;
import org.lowcoder.sdk.models.JsDatasourceConnectionConfig;
import org.lowcoder.sdk.models.Property;
import org.lowcoder.sdk.models.QueryExecutionResult;
import org.lowcoder.sdk.plugin.graphql.GraphQLDatasourceConfig;
Expand Down Expand Up @@ -263,7 +264,7 @@ public Mono<QueryExecutionResult> executeLibraryQueryFromJs(ServerWebExchange ex
Datasource datasource = tuple.getT3();
User user = tuple.getT4();
Mono<List<Property>> paramsAndHeadersInheritFromLogin = orgMember.isInvalid()
? Mono.empty() : getParamsAndHeadersInheritFromLogin(user, null);
? Mono.empty() : getParamsAndHeadersInheritFromLogin(user, null, false);

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

if(datasource.getDetailConfig() instanceof GraphQLDatasourceConfig graphQLDatasourceConfig && graphQLDatasourceConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
(user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId());
(user, ((OAuthInheritAuthConfig)graphQLDatasourceConfig.getAuthConfig()).getAuthId(), false);
}

if(datasource.getDetailConfig() instanceof JsDatasourceConnectionConfig jsDatasourceConnectionConfig
&& jsDatasourceConnectionConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
(user, jsDatasourceConnectionConfig.getAuthId(), true);
}

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

protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, String authId) {
protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, String authId, boolean isJsQuery) {
if(authId == null) {
return Mono.empty();
}
Expand All @@ -359,7 +366,11 @@ protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, St
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
return Mono.empty();
}
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
if(isJsQuery) {
return Mono.just(Collections.singletonList(new Property("OAUTH_ACCESS_TOKEN",activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
} else {
return Mono.just(Collections.singletonList(new Property("Authorization","Bearer " + activeConnectionOptional.get().getAuthConnectionAuthToken().getAccessToken(),"header")));
}
}

protected void onNextOrError(QueryExecutionRequest queryExecutionRequest, QueryVisitorContext queryVisitorContext, BaseQuery baseQuery,
Expand Down
2 changes: 1 addition & 1 deletion server/node-service/src/plugins/openApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export async function runOpenApi(

try {
const { parameters, requestBody } = normalizeParams(otherActionData, operation, isOas3Spec);
const securities = extractSecurityParams(dataSourceConfig.dynamicParamsConfig, definition);
let securities = extractSecurityParams(dataSourceConfig, definition);
const response = await SwaggerClient.execute({
spec: definition,
operationId: realOperationId,
Expand Down
2 changes: 1 addition & 1 deletion server/node-service/src/plugins/openApi/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { extractSecurityParams, getSchemaExample, extractLevelData, parseUrl } from "./util";

test("extractSecurityParams", () => {
const params = extractSecurityParams({ "ApiKeyAuth.value": "hello", ApiKeyAuth: null }, {
const params = extractSecurityParams({"dynamicParamsConfig":{ "ApiKeyAuth.value": "hello", ApiKeyAuth: null }}, {
openapi: "3.0",
components: {
securitySchemes: {
Expand Down
15 changes: 14 additions & 1 deletion server/node-service/src/plugins/openApi/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ interface NormalizedParams {
requestBody?: any;
}

export function extractSecurityParams(config: any, spec: OpenAPI.Document) {
export function extractSecurityParams(datasourceConfig: any, spec: OpenAPI.Document) {
const config = datasourceConfig.dynamicParamsConfig;
if (!config) {
return {};
}
Expand All @@ -96,6 +97,18 @@ export function extractSecurityParams(config: any, spec: OpenAPI.Document) {
names = Object.keys(swagger2Spec.securityDefinitions || {});
}
const authorized = _.pick(authData, names);

let oauthAccessToken = datasourceConfig["OAUTH_ACCESS_TOKEN"];

if(oauthAccessToken) {
return {
authorized: {
OAUTH_ACCESS_TOKEN: { value: oauthAccessToken }
},
specSecurity: []
};
}

return { authorized, specSecurity: spec.security };
}

Expand Down
7 changes: 5 additions & 2 deletions server/node-service/src/services/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,12 @@ export async function runPluginQuery(
const queryConfig = await getQueryConfig(plugin, dataSourceConfig);
const action = await evalToValue(queryConfig, dsl, context, dataSourceConfig);

//forward cookies
// forward cookies
context.forEach(({ key, value }) => {
if (dataSourceConfig.dynamicParamsConfig && key in dataSourceConfig.dynamicParamsConfig) {
// for oauth(inherit from login) support
if(key == "OAUTH_ACCESS_TOKEN") {
dataSourceConfig["OAUTH_ACCESS_TOKEN"] = value
} else if (dataSourceConfig.dynamicParamsConfig && key in dataSourceConfig.dynamicParamsConfig) {
const valueKey = `${key}.value`;
dataSourceConfig.dynamicParamsConfig[valueKey] = value[0].value
}
Expand Down

0 comments on commit 113c768

Please sign in to comment.