Skip to content

Add OAuth(Inherit From Login) Handling To Rest Api Datasource #611

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 2, 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
@@ -1,15 +1,6 @@
package org.lowcoder.domain.query.service;

import static org.lowcoder.sdk.exception.BizError.QUERY_EXECUTION_ERROR;
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_TIMEOUT;
import static org.lowcoder.sdk.util.ExceptionUtils.ofException;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
import org.lowcoder.domain.datasource.model.Datasource;
import org.lowcoder.domain.datasource.model.DatasourceConnectionHolder;
import org.lowcoder.domain.datasource.service.DatasourceConnectionPool;
Expand All @@ -24,10 +15,18 @@
import org.lowcoder.sdk.query.QueryVisitorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import static org.lowcoder.sdk.exception.BizError.QUERY_EXECUTION_ERROR;
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_TIMEOUT;
import static org.lowcoder.sdk.util.ExceptionUtils.ofException;

@Slf4j
@Service
public class QueryExecutionService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ public RestApiAuthType getAuthType() {
return RestApiAuthType.NO_AUTH;
}

public boolean isOauth2InheritFromLogin() {
if (this.authConfig != null) {
return this.authConfig.getType().name().equals(RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN.name());
}
return false;
}

public Set<String> getForwardCookies() {
return SetUtils.emptyIfNull(forwardCookies);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
@Type(value = BasicAuthConfig.class, name = "DIGEST_AUTH"),
@Type(value = BasicAuthConfig.class, name = "BASIC_AUTH"),
@Type(value = NoneAuthConfig.class, name = "NO_AUTH"),
@Type(value = DefaultAuthConfig.class, name = "OAUTH2_INHERIT_FROM_LOGIN")
@Type(value = OAuthInheritAuthConfig.class, name = "OAUTH2_INHERIT_FROM_LOGIN")
})
public abstract class AuthConfig implements Encrypt {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.lowcoder.sdk.plugin.restapi.auth;

import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;

import javax.annotation.Nullable;

/**
* oauth(inherit from login) auth config
*/
@Getter
public final class OAuthInheritAuthConfig extends AuthConfig {

private String authId;

@JsonCreator
public OAuthInheritAuthConfig(String authId, RestApiAuthType type) {
super(type);
this.authId = authId;
}

@Override
public AuthConfig mergeWithUpdatedConfig(@Nullable AuthConfig updatedConfig) {
// return new auth config if auth type changed
if (!(updatedConfig instanceof OAuthInheritAuthConfig oAuthInheritAuthConfig)) {
return updatedConfig;
}
// otherwise merge oauth auth config
return new OAuthInheritAuthConfig(oAuthInheritAuthConfig.getAuthId(),
oAuthInheritAuthConfig.getType());
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
package org.lowcoder.api.query;

import static org.lowcoder.domain.permission.model.ResourceAction.READ_APPLICATIONS;
import static org.lowcoder.sdk.exception.BizError.DATASOURCE_AND_APP_ORG_NOT_MATCH;
import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER;
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;

import java.util.List;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.api.home.SessionUserService;
Expand All @@ -27,11 +16,15 @@
import org.lowcoder.domain.query.service.LibraryQueryRecordService;
import org.lowcoder.domain.query.service.LibraryQueryService;
import org.lowcoder.domain.query.service.QueryExecutionService;
import org.lowcoder.domain.user.model.Connection;
import org.lowcoder.domain.user.model.User;
import org.lowcoder.infra.util.TupleUtils;
import org.lowcoder.sdk.config.CommonConfig;
import org.lowcoder.sdk.exception.BizError;
import org.lowcoder.sdk.models.Property;
import org.lowcoder.sdk.models.QueryExecutionResult;
import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig;
import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig;
import org.lowcoder.sdk.query.QueryVisitorContext;
import org.lowcoder.sdk.util.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -40,10 +33,21 @@
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;
import reactor.core.publisher.Timed;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.lowcoder.domain.permission.model.ResourceAction.READ_APPLICATIONS;
import static org.lowcoder.sdk.exception.BizError.DATASOURCE_AND_APP_ORG_NOT_MATCH;
import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER;
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;

@Service
public class ApplicationQueryApiService {

Expand Down Expand Up @@ -93,12 +97,13 @@ public Mono<QueryExecutionResult> executeApplicationQuery(ServerWebExchange exch
Mono<Datasource> datasourceMono = baseQueryMono.flatMap(query -> datasourceService.getById(query.getDatasourceId())
.switchIfEmpty(deferredError(BizError.DATASOURCE_NOT_FOUND, "DATASOURCE_NOT_FOUND", query.getDatasourceId())))
.cache();
return sessionUserService.getVisitorId()
.delayUntil(userId -> checkExecutePermission(userId, queryExecutionRequest.getPath(), appId,

return sessionUserService.getVisitor()
.delayUntil(user -> checkExecutePermission(user.getId(), queryExecutionRequest.getPath(), appId,
queryExecutionRequest.isViewMode()))
.zipWhen(visitorId -> Mono.zip(appMono, appQueryMono, baseQueryMono, datasourceMono), TupleUtils::merge)
.flatMap(tuple -> {
String userId = tuple.getT1();
String userId = tuple.getT1().getId();
Application app = tuple.getT2();
ApplicationQuery appQuery = tuple.getT3();
BaseQuery baseQuery = tuple.getT4();
Expand All @@ -109,8 +114,18 @@ public Mono<QueryExecutionResult> executeApplicationQuery(ServerWebExchange exch
}

MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies,
getAuthParamsAndHeadersInheritFromLogin(userId, app.getOrganizationId()), commonConfig.getDisallowedHosts());

Mono<List<Property>> paramsAndHeadersInheritFromLogin = Mono.empty();


// 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());

}

QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, app.getOrganizationId(), port, cookies, paramsAndHeadersInheritFromLogin, commonConfig.getDisallowedHosts());
return queryExecutionService.executeQuery(datasource, baseQuery.getQueryConfig(), queryExecutionRequest.paramMap(),
appQuery.getTimeoutStr(), queryVisitorContext
)
Expand Down Expand Up @@ -176,8 +191,18 @@ private Mono<BaseQuery> getBaseQueryFromLibraryQuery(ApplicationQuery query) {
.map(LibraryQueryRecord::getQuery);
}

protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(String userId, String orgId) {
return Mono.empty();
protected Mono<List<Property>> getAuthParamsAndHeadersInheritFromLogin(User user, String authId) {
if(authId == null) {
return Mono.empty();
}
Optional<Connection> activeConnectionOptional = user.getConnections()
.stream()
.filter(connection -> connection.getAuthId().equals(authId))
.findFirst();
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
return Mono.empty();
}
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 @@ -6,9 +6,7 @@
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -23,6 +21,7 @@
import org.lowcoder.api.usermanagement.OrgDevChecker;
import org.lowcoder.api.util.BusinessEventPublisher;
import org.lowcoder.api.util.ViewBuilder;
import org.lowcoder.domain.authentication.AuthenticationService;
import org.lowcoder.domain.datasource.model.Datasource;
import org.lowcoder.domain.datasource.service.DatasourceService;
import org.lowcoder.domain.organization.model.OrgMember;
Expand All @@ -35,13 +34,16 @@
import org.lowcoder.domain.query.service.LibraryQueryRecordService;
import org.lowcoder.domain.query.service.LibraryQueryService;
import org.lowcoder.domain.query.service.QueryExecutionService;
import org.lowcoder.domain.user.model.Connection;
import org.lowcoder.domain.user.model.User;
import org.lowcoder.domain.user.service.UserService;
import org.lowcoder.sdk.config.CommonConfig;
import org.lowcoder.sdk.exception.BizError;
import org.lowcoder.sdk.exception.PluginCommonError;
import org.lowcoder.sdk.models.Property;
import org.lowcoder.sdk.models.QueryExecutionResult;
import org.lowcoder.sdk.plugin.restapi.RestApiDatasourceConfig;
import org.lowcoder.sdk.plugin.restapi.auth.OAuthInheritAuthConfig;
import org.lowcoder.sdk.query.QueryVisitorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -87,6 +89,9 @@ public class LibraryQueryApiService {
@Autowired
private CommonConfig commonConfig;

@Autowired
private AuthenticationService authenticationService;

@Value("${server.port}")
private int port;

Expand Down Expand Up @@ -245,15 +250,19 @@ public Mono<QueryExecutionResult> executeLibraryQueryFromJs(ServerWebExchange ex

Mono<OrgMember> visitorOrgMemberCache = sessionUserService.getVisitorOrgMemberCache()
.onErrorReturn(NOT_EXIST);
return Mono.zip(visitorOrgMemberCache, baseQueryMono, datasourceMono)

Mono<User> userMono = sessionUserService.getVisitor();

return Mono.zip(visitorOrgMemberCache, baseQueryMono, datasourceMono, userMono)
.flatMap(tuple -> {
OrgMember orgMember = tuple.getT1();
String orgId = orgMember.getOrgId();
String userId = orgMember.getUserId();
BaseQuery baseQuery = tuple.getT2();
Datasource datasource = tuple.getT3();
User user = tuple.getT4();
Mono<List<Property>> paramsAndHeadersInheritFromLogin = orgMember.isInvalid()
? Mono.empty() : getParamsAndHeadersInheritFromLogin(userId, orgId);
? Mono.empty() : getParamsAndHeadersInheritFromLogin(user, null);

QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port,
exchange.getRequest().getCookies(),
Expand Down Expand Up @@ -284,17 +293,29 @@ public Mono<QueryExecutionResult> executeLibraryQuery(ServerWebExchange exchange
Mono<Datasource> datasourceMono = baseQueryMono.flatMap(query -> datasourceService.getById(query.getDatasourceId())
.switchIfEmpty(deferredError(BizError.DATASOURCE_NOT_FOUND, "DATASOURCE_NOT_FOUND", query.getDatasourceId()))).cache();

Mono<User> userMono = sessionUserService.getVisitor();

return orgDevChecker.checkCurrentOrgDev()
.then(Mono.zip(sessionUserService.getVisitorOrgMemberCache(),
baseQueryMono, datasourceMono))
baseQueryMono, datasourceMono, userMono))
.flatMap(tuple -> {
OrgMember orgMember = tuple.getT1();
String orgId = orgMember.getOrgId();
String userId = orgMember.getUserId();
BaseQuery baseQuery = tuple.getT2();
Datasource datasource = tuple.getT3();
Mono<List<Property>> paramsAndHeadersInheritFromLogin =
getParamsAndHeadersInheritFromLogin(userId, orgId);
User user = tuple.getT4();

Mono<List<Property>> paramsAndHeadersInheritFromLogin = Mono.empty();


// check if oauth inherited from login and save token
if(datasource.getDetailConfig() instanceof RestApiDatasourceConfig restApiDatasourceConfig
&& restApiDatasourceConfig.isOauth2InheritFromLogin()) {
paramsAndHeadersInheritFromLogin = getParamsAndHeadersInheritFromLogin
(user, ((OAuthInheritAuthConfig)restApiDatasourceConfig.getAuthConfig()).getAuthId());
}

QueryVisitorContext queryVisitorContext = new QueryVisitorContext(userId, orgId, port, cookies, paramsAndHeadersInheritFromLogin,
commonConfig.getDisallowedHosts());
Map<String, Object> queryConfig = baseQuery.getQueryConfig();
Expand Down Expand Up @@ -322,8 +343,18 @@ private Mono<BaseQuery> getBaseQuery(LibraryQueryCombineId libraryQueryCombineId
.map(LibraryQueryRecord::getQuery);
}

protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(String userId, String orgId) {
return Mono.empty();
protected Mono<List<Property>> getParamsAndHeadersInheritFromLogin(User user, String authId) {
if(authId == null) {
return Mono.empty();
}
Optional<Connection> activeConnectionOptional = user.getConnections()
.stream()
.filter(connection -> connection.getAuthId().equals(authId))
.findFirst();
if(!activeConnectionOptional.isPresent() || activeConnectionOptional.get().getAuthConnectionAuthToken() == null) {
return Mono.empty();
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ spring:
mongodb:
authentication-database: admin
auto-index-creation: false
uri: mongodb://192.168.8.103:27017/lowcoder?authSource=admin
uri: mongodb://localhost:27017/lowcoder?authSource=admin
redis:
url: redis://192.168.8.103:6379
url: redis://localhost:6379
main:
allow-bean-definition-overriding: true
allow-circular-references: true
Expand Down