Skip to content

Add Oauth Handling For Open API Datasources #624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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