diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 94ebcdcea..574e3334c 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -204,8 +204,8 @@ RUN mkdir -p /etc/apt/keyrings \ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y bash gnupg curl lsb-release \ && curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb bookworm main" | tee /etc/apt/sources.list.d/redis.list \ - && curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/4.4 main" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list \ + && curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/7.0 main" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \ && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends -y \ mongodb-org \ redis \ diff --git a/deploy/docker/docker-compose-multi.yaml b/deploy/docker/docker-compose-multi.yaml index 14eb5c84b..b3bb6b929 100644 --- a/deploy/docker/docker-compose-multi.yaml +++ b/deploy/docker/docker-compose-multi.yaml @@ -60,6 +60,17 @@ services: # LOWCODER_API_KEY_SECRET: "5a41b090758b39b226603177ef48d73ae9839dd458ccb7e66f7e7cc028d5a50b" LOWCODER_WORKSPACE_MODE: SAAS + # Lowcoder notification emails setup + LOWCODER_ADMIN_SMTP_HOST: smtp.gmail.com + LOWCODER_ADMIN_SMTP_PORT: 587 + LOWCODER_ADMIN_SMTP_USERNAME: + LOWCODER_ADMIN_SMTP_PASSWORD: + LOWCODER_ADMIN_SMTP_AUTH: true + LOWCODER_ADMIN_SMTP_SSL_ENABLED: false + LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED: true + LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED: true + # Email used as sender in lost password email + LOWCODER_EMAIL_NOTIFICATIONS_SENDER: info@localhost restart: unless-stopped depends_on: - mongodb diff --git a/deploy/docker/docker-compose.yaml b/deploy/docker/docker-compose.yaml index ca0a316eb..092f3027f 100644 --- a/deploy/docker/docker-compose.yaml +++ b/deploy/docker/docker-compose.yaml @@ -12,6 +12,8 @@ services: - "3443:3443" # - "27017:27017" environment: + # Public base url + LOWCODER_PUBLIC_URL: "http://localhost:3000/" # enable services LOWCODER_REDIS_ENABLED: "true" LOWCODER_MONGODB_ENABLED: "true" @@ -59,6 +61,17 @@ services: LOWCODER_MAX_REQUEST_SIZE: 20m LOWCODER_MAX_QUERY_TIMEOUT: 120 LOWCODER_WORKSPACE_MODE: SAAS + # Lowcoder notification emails setup + LOWCODER_ADMIN_SMTP_HOST: smtp.gmail.com + LOWCODER_ADMIN_SMTP_PORT: 587 + LOWCODER_ADMIN_SMTP_USERNAME: + LOWCODER_ADMIN_SMTP_PASSWORD: + LOWCODER_ADMIN_SMTP_AUTH: true + LOWCODER_ADMIN_SMTP_SSL_ENABLED: false + LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED: true + LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED: true + # Email used as sender in lost password email + LOWCODER_EMAIL_NOTIFICATIONS_SENDER: info@localhost volumes: - ./lowcoder-stacks:/lowcoder-stacks - ./lowcoder-stacks/assets:/lowcoder/assets diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java index 66a0f41e0..7c1ad43df 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java @@ -32,6 +32,8 @@ public interface ApplicationRepository extends ReactiveMongoRepository findByIdIn(Collection ids); + Flux findByCreatedByAndIdIn(String userId, Collection ids); + /** * Filter public applications from list of supplied IDs */ diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java index 3bdcbac9f..f89a99172 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java @@ -52,7 +52,7 @@ public interface ApplicationService { @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - Mono> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection applicationIds, boolean isAnonymous, Boolean isPrivateMarketplace); + Mono> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection applicationIds, String userId, Boolean isPrivateMarketplace); @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") @@ -60,7 +60,7 @@ public interface ApplicationService { @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - Mono> getPrivateApplicationIds(Collection applicationIds); + Mono> getPrivateApplicationIds(Collection applicationIds, String userId); @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationServiceImpl.java index a7224e946..786536fd1 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationServiceImpl.java @@ -7,12 +7,17 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.lowcoder.domain.application.model.Application; import org.lowcoder.domain.application.model.ApplicationRequestType; import org.lowcoder.domain.application.model.ApplicationStatus; import org.lowcoder.domain.application.repository.ApplicationRepository; +import org.lowcoder.domain.organization.repository.OrganizationRepository; +import org.lowcoder.domain.organization.service.OrgMemberService; import org.lowcoder.domain.permission.model.ResourceRole; import org.lowcoder.domain.permission.service.ResourcePermissionService; +import org.lowcoder.domain.user.repository.UserRepository; +import org.lowcoder.domain.user.service.UserService; import org.lowcoder.infra.annotation.NonEmptyMono; import org.lowcoder.infra.mongo.MongoUpsertHelper; import org.lowcoder.sdk.constants.FieldName; @@ -37,6 +42,7 @@ public class ApplicationServiceImpl implements ApplicationService { private final MongoUpsertHelper mongoUpsertHelper; private final ResourcePermissionService resourcePermissionService; private final ApplicationRepository repository; + private final UserRepository userRepository; @Override public Mono findById(String id) { @@ -219,8 +225,8 @@ public Mono setApplicationAsAgencyProfile(String applicationId, boolean @Override @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - public Mono> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection applicationIds, boolean isAnonymous, Boolean isPrivateMarketplace) { - + public Mono> getFilteredPublicApplicationIds(ApplicationRequestType requestType, Collection applicationIds, String userId, Boolean isPrivateMarketplace) { + boolean isAnonymous = StringUtils.isBlank(userId); switch(requestType) { case PUBLIC_TO_ALL: @@ -230,7 +236,7 @@ public Mono> getFilteredPublicApplicationIds(ApplicationRequestType } else { - return getPrivateApplicationIds(applicationIds); + return getPrivateApplicationIds(applicationIds, userId); } case PUBLIC_TO_MARKETPLACE: return getPublicMarketplaceApplicationIds(applicationIds, isAnonymous, isPrivateMarketplace); @@ -262,11 +268,16 @@ public Mono> getPublicApplicationIds(Collection applicationI @Override @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - public Mono> getPrivateApplicationIds(Collection applicationIds) { + public Mono> getPrivateApplicationIds(Collection applicationIds, String userId) { + // TODO: in 2.4.0 we need to check whether the app was published or not - return repository.findByIdIn(applicationIds) + return repository.findByCreatedByAndIdIn(userId, applicationIds) .map(HasIdAndAuditing::getId) .collect(Collectors.toSet()); + +// return repository.findByIdIn(applicationIds) +// .map(HasIdAndAuditing::getId) +// .collect(Collectors.toSet()); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java index 7c92cc3d4..011c18410 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java @@ -53,10 +53,10 @@ protected Mono>> getAnonymousUserPermission // This is for PTM apps that are public but only available to logged-in users @Override protected Mono>> getNonAnonymousUserPublicResourcePermissions - (Collection resourceIds, ResourceAction resourceAction) { + (Collection resourceIds, ResourceAction resourceAction, String userId) { Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getPrivateApplicationIds(applicationIds), + return Mono.zip(applicationService.getPrivateApplicationIds(applicationIds, userId), templateSolutionService.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); @@ -75,7 +75,7 @@ protected Mono>> getAnonymousUserApplicatio } Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode()) + return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, null, config.getMarketplace().isPrivateMode()) .defaultIfEmpty(new HashSet<>()), templateSolutionService.getTemplateApplicationIds(applicationIds) .defaultIfEmpty(new HashSet<>()) @@ -88,9 +88,9 @@ protected Mono>> getAnonymousUserApplicatio @Override protected Mono>> getNonAnonymousUserApplicationPublicResourcePermissions( - Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) { + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId) { Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()), + return Mono.zip(applicationService.getFilteredPublicApplicationIds(requestType, applicationIds, userId, config.getMarketplace().isPrivateMode()), templateSolutionService.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java index 200a9b6a8..24b3475d5 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/DatasourcePermissionHandler.java @@ -36,7 +36,7 @@ protected Mono>> getAnonymousUserPermission } @Override - protected Mono>> getNonAnonymousUserPublicResourcePermissions(Collection resourceIds, ResourceAction resourceAction) { + protected Mono>> getNonAnonymousUserPublicResourcePermissions(Collection resourceIds, ResourceAction resourceAction, String userId) { return Mono.just(Collections.emptyMap()); } @@ -48,7 +48,7 @@ protected Mono>> getAnonymousUserApplicatio @Override protected Mono>> getNonAnonymousUserApplicationPublicResourcePermissions( - Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType) { + Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId) { return Mono.just(Collections.emptyMap()); } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java index 115d74eb0..2ec65a080 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java @@ -88,7 +88,7 @@ public Mono checkUserPermissionStatusOnResource( return publicResourcePermissionMono; } - Mono nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserPublicResourcePermissions(singletonList(resourceId), resourceAction) + Mono nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserPublicResourcePermissions(singletonList(resourceId), resourceAction, userId) .map(it -> it.getOrDefault(resourceId, emptyList())) .map(it -> { if (!it.isEmpty()) { @@ -141,13 +141,13 @@ protected abstract Mono>> getAnonymousUserP ResourceAction resourceAction); protected abstract Mono>> getNonAnonymousUserPublicResourcePermissions - (Collection resourceIds, ResourceAction resourceAction); + (Collection resourceIds, ResourceAction resourceAction, String userId); protected abstract Mono>> getAnonymousUserApplicationPermissions(Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType); protected abstract Mono>> getNonAnonymousUserApplicationPublicResourcePermissions - (Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType); + (Collection resourceIds, ResourceAction resourceAction, ApplicationRequestType requestType, String userId); private Mono>> getAllMatchingPermissions0(String userId, String orgId, ResourceType resourceType, @@ -229,7 +229,7 @@ public Mono checkUserPermissionStatusOnApplicati return publicResourcePermissionMono; } - Mono nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserApplicationPublicResourcePermissions(singletonList(resourceId), resourceAction, requestType) + Mono nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserApplicationPublicResourcePermissions(singletonList(resourceId), resourceAction, requestType, userId) .map(it -> it.getOrDefault(resourceId, emptyList())) .map(it -> { if (!it.isEmpty()) { diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/model/User.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/model/User.java index 821261bd8..9b516065a 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/model/User.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/model/User.java @@ -1,16 +1,13 @@ package org.lowcoder.domain.user.model; -import static com.google.common.base.Suppliers.memoize; -import static org.lowcoder.infra.util.AssetUtils.toAssetPath; - -import java.time.Instant; -import java.util.*; -import java.util.function.Supplier; - +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; import org.lowcoder.domain.mongodb.AfterMongodbRead; @@ -23,9 +20,12 @@ import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.core.mapping.Document; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import java.util.*; +import java.util.function.Supplier; + +import static com.google.common.base.Suppliers.memoize; +import static org.lowcoder.infra.util.AssetUtils.toAssetPath; @Getter @@ -42,8 +42,7 @@ public class User extends HasIdAndAuditing implements BeforeMongodbWrite, AfterM private String name; - @Builder.Default - private String uiLanguage = UiConstants.DEFAULT_UI_LANGUAGE; + private String uiLanguage; private String avatar; @@ -79,8 +78,7 @@ public class User extends HasIdAndAuditing implements BeforeMongodbWrite, AfterM /** * Only used for mongodb (de)serialization */ - @Builder.Default - private List apiKeys = new ArrayList<>(); + private List apiKeys; @Transient @JsonIgnore @@ -143,15 +141,25 @@ public void markAsDeleted() { @Override public void beforeMongodbWrite(MongodbInterceptorContext context) { - this.apiKeysList.forEach(apiKey -> apiKey.doEncrypt(s -> context.encryptionService().encryptString(s))); - apiKeys = JsonUtils.fromJsonSafely(JsonUtils.toJsonSafely(apiKeysList, SerializeConfig.JsonViews.Internal.class), new TypeReference<>() { - }, new ArrayList<>()); + if (CollectionUtils.isNotEmpty(this.apiKeysList)) { + this.apiKeysList.forEach(apiKey -> apiKey.doEncrypt(s -> context.encryptionService().encryptString(s))); + apiKeys = JsonUtils.fromJsonSafely(JsonUtils.toJsonSafely(apiKeysList, SerializeConfig.JsonViews.Internal.class), new TypeReference<>() { + }, new ArrayList<>()); + } } @Override public void afterMongodbRead(MongodbInterceptorContext context) { - this.apiKeysList = JsonUtils.fromJsonSafely(JsonUtils.toJson(apiKeys), new TypeReference<>() { - }, new ArrayList<>()); - this.apiKeysList.forEach(authConfig -> authConfig.doDecrypt(s -> context.encryptionService().decryptString(s))); + if (CollectionUtils.isNotEmpty(apiKeys)) + { + this.apiKeysList = JsonUtils.fromJsonSafely(JsonUtils.toJson(apiKeys), new TypeReference<>() { + }, new ArrayList<>()); + this.apiKeysList.forEach(authConfig -> authConfig.doDecrypt(s -> context.encryptionService().decryptString(s))); + } + + /** set UI language to default one if it's null **/ + if (StringUtils.isBlank(this.uiLanguage)) { + this.uiLanguage = UiConstants.DEFAULT_UI_LANGUAGE; + } } } diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2GenericAuthConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2GenericAuthConfig.java new file mode 100644 index 000000000..202e01c08 --- /dev/null +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2GenericAuthConfig.java @@ -0,0 +1,23 @@ +package org.lowcoder.sdk.auth; + +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +/** + * This class is for Generic Auth Provider + */ +@Getter +@SuperBuilder +@Jacksonized +public class Oauth2GenericAuthConfig extends Oauth2SimpleAuthConfig { + private String sourceDescription; + private String sourceIcon; + private String sourceCategory; + private String issuerUri; + private String authorizationEndpoint; + private String tokenEndpoint; + private String userInfoEndpoint; + private String scope; + private Boolean userInfoIntrospection; +} diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2SimpleAuthConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2SimpleAuthConfig.java index 25dad17a6..c93b7eb6b 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2SimpleAuthConfig.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/Oauth2SimpleAuthConfig.java @@ -38,6 +38,7 @@ public String getAuthorizeUrl() { case AuthTypeConstants.GITHUB -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.GITHUB_AUTHORIZE_URL); case AuthTypeConstants.ORY -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.ORY_AUTHORIZE_URL); case AuthTypeConstants.KEYCLOAK -> replaceAuthUrlClientIdPlaceholder(Oauth2Constants.KEYCLOAK_AUTHORIZE_URL); + case AuthTypeConstants.GENERIC -> ((Oauth2GenericAuthConfig)this).getAuthorizationEndpoint(); default -> null; }; } diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/constants/AuthTypeConstants.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/constants/AuthTypeConstants.java index f2d4076bd..4f7d209c9 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/constants/AuthTypeConstants.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/auth/constants/AuthTypeConstants.java @@ -10,4 +10,5 @@ public class AuthTypeConstants { public static final String GITHUB = "GITHUB"; public static final String ORY = "ORY"; public static final String KEYCLOAK = "KEYCLOAK"; + public static final String GENERIC = "GENERIC"; } diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java index b6fed12ee..bc49625c7 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java @@ -141,8 +141,8 @@ public static class Workspace { @Data public static class Cookie { - - private long maxAgeInSeconds = Duration.ofDays(30).toSeconds(); + //Set cookie max age to 1 day + private long maxAgeInSeconds = Duration.ofDays(1).toSeconds(); } @Data diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/JsonUtils.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/JsonUtils.java index 0c4dd861b..6c5084556 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/JsonUtils.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/JsonUtils.java @@ -13,10 +13,7 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; -import org.lowcoder.sdk.auth.EmailAuthConfig; -import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig; -import org.lowcoder.sdk.auth.Oauth2OryAuthConfig; -import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig; +import org.lowcoder.sdk.auth.*; import java.nio.charset.StandardCharsets; import java.util.List; @@ -41,6 +38,7 @@ public final class JsonUtils { OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2SimpleAuthConfig.class, GOOGLE)); OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2OryAuthConfig.class, ORY)); OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2KeycloakAuthConfig.class, KEYCLOAK)); + OBJECT_MAPPER.registerSubtypes(new NamedType(Oauth2GenericAuthConfig.class, GENERIC)); } public static final JsonNode EMPTY_JSON_NODE = createObjectNode(); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java index 81dbd09b5..b66fe752a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java @@ -1,6 +1,7 @@ package org.lowcoder.api.authentication; import java.util.List; +import java.util.Map; import org.lowcoder.api.authentication.dto.APIKeyRequest; import org.lowcoder.api.authentication.dto.AuthConfigRequest; @@ -14,7 +15,9 @@ import org.lowcoder.domain.authentication.FindAuthConfig; import org.lowcoder.domain.user.model.APIKey; import org.lowcoder.sdk.auth.AbstractAuthConfig; +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; import org.lowcoder.sdk.util.CookieHelper; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -127,5 +130,4 @@ public Mono>> getAllAPIKeys() { .collectList() .map(ResponseView::success); } - } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationEndpoints.java index d66e252ae..3a00320cd 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationEndpoints.java @@ -1,6 +1,7 @@ package org.lowcoder.api.authentication; import java.util.List; +import java.util.Map; import org.lowcoder.api.authentication.dto.APIKeyRequest; import org.lowcoder.api.authentication.dto.AuthConfigRequest; @@ -11,8 +12,10 @@ import org.lowcoder.domain.user.model.APIKey; import org.lowcoder.infra.constant.NewUrl; import org.lowcoder.sdk.auth.AbstractAuthConfig; +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; import org.lowcoder.sdk.config.SerializeConfig.JsonViews; import org.lowcoder.sdk.constants.AuthSourceConstants; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -159,5 +162,4 @@ public Mono> linkAccountWithThirdParty( */ public record FormLoginRequest(String loginId, String password, boolean register, String source, String authId) { } - } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/dto/AuthConfigRequest.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/dto/AuthConfigRequest.java index 3b8cb98fa..93452c1f8 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/dto/AuthConfigRequest.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/dto/AuthConfigRequest.java @@ -1,6 +1,7 @@ package org.lowcoder.api.authentication.dto; import jakarta.annotation.Nullable; +import jakarta.validation.constraints.Null; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -27,6 +28,30 @@ public boolean isEnableRegister() { return MapUtils.getBoolean(this, "enableRegister", true); } + /** + * Additional configs for generic + * config will be updated instead of creating a new one. + */ + @Nullable + public String getIssuerUri() { + return getString("issuerUri"); + } + + @Nullable + public String getAuthorizationEndpoint() { + return getString("authorizationEndpoint"); + } + + @Nullable + public String getTokenEndpoint() { + return getString("tokenEndpoint"); + } + + @Nullable + public String getUserInfoEndpoint() { + return getString("userInfoEndpoint"); + } + @Nullable public String getInstanceId() { return getString("instanceId"); @@ -42,6 +67,11 @@ public String getClientSecret() { return getString("clientSecret"); } + @Nullable + public String getScope() { + return getString("scope"); + } + public String getSource(String defaultValue) { String source = getString("source"); if (StringUtils.isNotBlank(source)) { @@ -58,7 +88,20 @@ public String getSourceName(String defaultValue) { return defaultValue; } + public String getSourceDescription() { + return getString("sourceDescription"); + } + + public String getSourceIcon() { + return getString("sourceIcon"); + } + + public String getSourceCategory() { + return getString("sourceDescription"); + } + public String getString(String key) { return MapUtils.getString(this, key); } + } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/GenericOAuthProviderSource.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/GenericOAuthProviderSource.java new file mode 100644 index 000000000..2963a5ea7 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/GenericOAuthProviderSource.java @@ -0,0 +1,31 @@ +package org.lowcoder.api.authentication.request.oauth2; + +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; + +/** + * This class is the implementation of Oauth2Source and uses an instance of GenericOAuthProviderConfig + * to return the appropriate URLs + */ +public class GenericOAuthProviderSource implements Oauth2Source { + + private final Oauth2GenericAuthConfig config; + + public GenericOAuthProviderSource(Oauth2GenericAuthConfig config) { + this.config = config; + } + + @Override + public String accessToken() { + return config.getTokenEndpoint(); + } + + @Override + public String userInfo() { + return config.getUserInfoEndpoint(); + } + + @Override + public String refresh() { + return config.getTokenEndpoint(); + } +} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java index c8fdae9b2..5444d203f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/Oauth2AuthRequestFactory.java @@ -4,11 +4,8 @@ import org.lowcoder.api.authentication.request.AuthRequest; import org.lowcoder.api.authentication.request.AuthRequestFactory; -import org.lowcoder.api.authentication.request.oauth2.request.AbstractOauth2Request; -import org.lowcoder.api.authentication.request.oauth2.request.GithubRequest; -import org.lowcoder.api.authentication.request.oauth2.request.GoogleRequest; -import org.lowcoder.api.authentication.request.oauth2.request.KeycloakRequest; -import org.lowcoder.api.authentication.request.oauth2.request.OryRequest; +import org.lowcoder.api.authentication.request.oauth2.request.*; +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig; import org.lowcoder.sdk.auth.Oauth2OryAuthConfig; import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig; @@ -32,6 +29,7 @@ private AbstractOauth2Request buildRequest(OAu case GOOGLE -> new GoogleRequest((Oauth2SimpleAuthConfig) context.getAuthConfig()); case ORY -> new OryRequest((Oauth2OryAuthConfig) context.getAuthConfig()); case KEYCLOAK -> new KeycloakRequest((Oauth2KeycloakAuthConfig)context.getAuthConfig()); + case GENERIC -> new GenericAuthRequest((Oauth2GenericAuthConfig) context.getAuthConfig()); default -> throw new UnsupportedOperationException(context.getAuthConfig().getAuthType()); }; } @@ -42,6 +40,7 @@ public Set supportedAuthTypes() { GITHUB, GOOGLE, ORY, - KEYCLOAK); + KEYCLOAK, + GENERIC); } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/request/GenericAuthRequest.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/request/GenericAuthRequest.java new file mode 100644 index 000000000..bdf7305c4 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/request/oauth2/request/GenericAuthRequest.java @@ -0,0 +1,90 @@ +package org.lowcoder.api.authentication.request.oauth2.request; + +import org.lowcoder.api.authentication.request.AuthException; +import org.lowcoder.api.authentication.request.oauth2.GenericOAuthProviderSource; +import org.lowcoder.api.authentication.request.oauth2.OAuth2RequestContext; +import org.lowcoder.domain.user.model.AuthToken; +import org.lowcoder.domain.user.model.AuthUser; +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; +import org.lowcoder.sdk.util.JsonUtils; +import org.lowcoder.sdk.webclient.WebClientBuildHelper; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; +import reactor.core.publisher.Mono; + +import java.util.Map; + +import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthToken; +import static org.lowcoder.api.authentication.util.AuthenticationUtils.mapToAuthUser; + +/** + * This class is for Generic Auth Request + */ +public class GenericAuthRequest extends AbstractOauth2Request{ + + public GenericAuthRequest(Oauth2GenericAuthConfig context) { + super(context, new GenericOAuthProviderSource(context)); + } + + @Override + protected Mono getAuthToken(OAuth2RequestContext context) { + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .post() + .uri(config.getTokenEndpoint()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData("code", context.getCode()) + .with("client_id", config.getClientId()) + .with("client_secret", config.getClientSecret()) + .with("grant_type", "authorization_code") + .with("redirect_uri", context.getRedirectUrl())) + .retrieve() + .bodyToMono(Map.class) + .flatMap(map -> { + if (map.containsKey("error") || map.containsKey("error_description")) { + return Mono.error(new AuthException(JsonUtils.toJson(map))); + } + return Mono.just(mapToAuthToken(map)); + }); + } + + @Override + protected Mono refreshAuthToken(String refreshToken) { + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .post() + .uri(config.getTokenEndpoint()) + .body(BodyInserters.fromFormData("grant_type", "refresh_token") + .with("refresh_token", refreshToken) + .with("client_id", config.getClientId()) + .with("client_secret", config.getClientSecret())) + .retrieve() + .bodyToMono(Map.class) + .flatMap(map -> { + if (map.containsKey("error") || map.containsKey("error_description")) { + return Mono.error(new AuthException(JsonUtils.toJson(map))); + } + return Mono.just(mapToAuthToken(map)); + }); + } + + @Override + protected Mono getAuthUser(AuthToken authToken) { + return WebClientBuildHelper.builder() + .systemProxy() + .build() + .get() + .uri(config.getUserInfoEndpoint()) + .headers(headers -> headers.setBearerAuth(authToken.getAccessToken())) + .retrieve() + .bodyToMono(Map.class) + .flatMap(map -> { + if (map.containsKey("error") || map.containsKey("error_description")) { + return Mono.error(new AuthException(JsonUtils.toJson(map))); + } + return Mono.just(mapToAuthUser(map)); + }); + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java index 110831c98..3072c133c 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java @@ -31,10 +31,13 @@ import org.lowcoder.domain.user.model.*; import org.lowcoder.domain.user.service.UserService; import org.lowcoder.sdk.auth.AbstractAuthConfig; +import org.lowcoder.sdk.auth.Oauth2GenericAuthConfig; +import org.lowcoder.sdk.auth.constants.AuthTypeConstants; import org.lowcoder.sdk.config.AuthProperties; import org.lowcoder.sdk.exception.BizError; import org.lowcoder.sdk.exception.BizException; import org.lowcoder.sdk.util.CookieHelper; +import org.lowcoder.sdk.webclient.WebClientBuildHelper; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.web.server.ServerWebExchange; @@ -330,7 +333,6 @@ public Flux findAPIKeys() { ); } - private Mono removeTokensByAuthId(String authId) { return sessionUserService.getVisitorOrgMemberCache() .flatMapMany(orgMember -> orgMemberService.getOrganizationMembers(orgMember.getOrgId())) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/factory/AuthConfigFactoryImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/factory/AuthConfigFactoryImpl.java index be3854df1..4e9eb7f13 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/factory/AuthConfigFactoryImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/factory/AuthConfigFactoryImpl.java @@ -10,11 +10,7 @@ import org.apache.commons.collections4.MapUtils; import org.lowcoder.api.authentication.dto.AuthConfigRequest; -import org.lowcoder.sdk.auth.AbstractAuthConfig; -import org.lowcoder.sdk.auth.EmailAuthConfig; -import org.lowcoder.sdk.auth.Oauth2KeycloakAuthConfig; -import org.lowcoder.sdk.auth.Oauth2OryAuthConfig; -import org.lowcoder.sdk.auth.Oauth2SimpleAuthConfig; +import org.lowcoder.sdk.auth.*; import org.lowcoder.sdk.auth.constants.AuthTypeConstants; import org.springframework.stereotype.Component; @@ -23,12 +19,14 @@ public class AuthConfigFactoryImpl implements AuthConfigFactory { @Override public AbstractAuthConfig build(AuthConfigRequest authConfigRequest, boolean enable) { + buildOauth2GenericAuthConfig(authConfigRequest, enable); return switch (authConfigRequest.getAuthType()) { case AuthTypeConstants.FORM -> buildEmailAuthConfig(authConfigRequest, enable); case AuthTypeConstants.GITHUB -> buildOauth2SimpleAuthConfig(GITHUB, GITHUB_NAME, authConfigRequest, enable); case AuthTypeConstants.GOOGLE -> buildOauth2SimpleAuthConfig(GOOGLE, GOOGLE_NAME, authConfigRequest, enable); case AuthTypeConstants.ORY -> buildOauth2OryAuthConfig(authConfigRequest, enable); case AuthTypeConstants.KEYCLOAK -> buildOauth2KeycloakAuthConfig(authConfigRequest, enable); + case AuthTypeConstants.GENERIC -> buildOauth2GenericAuthConfig(authConfigRequest, enable); default -> throw new UnsupportedOperationException(authConfigRequest.getAuthType()); }; } @@ -40,7 +38,8 @@ public Set supportAuthTypes() { AuthTypeConstants.GITHUB, AuthTypeConstants.GOOGLE, AuthTypeConstants.ORY, - AuthTypeConstants.KEYCLOAK + AuthTypeConstants.KEYCLOAK, + AuthTypeConstants.GENERIC ); } @@ -93,5 +92,32 @@ private Oauth2SimpleAuthConfig buildOauth2KeycloakAuthConfig(AuthConfigRequest a .authType(authConfigRequest.getAuthType()) .build(); } - + + /** + * This method is to build Oauth2GenericAuth config + * @param authConfigRequest AuthConfigRequest + * @param enable boolean + * @return Oauth2SimpleAuthConfig + */ + private Oauth2SimpleAuthConfig buildOauth2GenericAuthConfig(AuthConfigRequest authConfigRequest, boolean enable) { + return Oauth2GenericAuthConfig.builder() + .id(authConfigRequest.getId()) + .enable(enable) + .enableRegister(authConfigRequest.isEnableRegister()) + .source(authConfigRequest.getSource(AuthTypeConstants.GENERIC)) + .sourceName(authConfigRequest.getSourceName(AuthTypeConstants.GENERIC)) + .sourceDescription(authConfigRequest.getSourceDescription()) + .sourceIcon(authConfigRequest.getSourceIcon()) + .sourceCategory(authConfigRequest.getSourceCategory()) + .clientId(requireNonNull(authConfigRequest.getClientId(), "clientId can not be null.")) + .clientSecret(authConfigRequest.getClientSecret()) + .issuerUri(authConfigRequest.getIssuerUri()) + .authorizationEndpoint(authConfigRequest.getAuthorizationEndpoint()) + .tokenEndpoint(authConfigRequest.getTokenEndpoint()) + .userInfoEndpoint(authConfigRequest.getUserInfoEndpoint()) + .scope(authConfigRequest.getScope()) + .authType(AuthTypeConstants.GENERIC) + .userInfoIntrospection(MapUtils.getBoolean(authConfigRequest,"userInfoIntrospection")) + .build(); + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AuthenticationUtils.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AuthenticationUtils.java index 6ef443ac9..e828cb588 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AuthenticationUtils.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/util/AuthenticationUtils.java @@ -4,7 +4,11 @@ import static reactor.core.scheduler.Schedulers.newBoundedElastic; import java.util.Collection; +import java.util.Map; +import org.apache.commons.collections4.MapUtils; +import org.lowcoder.domain.user.model.AuthToken; +import org.lowcoder.domain.user.model.AuthUser; import org.lowcoder.domain.user.model.User; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -57,4 +61,29 @@ public String getName() { }; } + /** + * Utility method to map from Map to AuthToken + * @param map Object + * @return AuthToken + */ + public static AuthToken mapToAuthToken(Map map) { + return AuthToken.builder() + .accessToken(MapUtils.getString(map, "access_token")) + .expireIn(MapUtils.getIntValue(map, "expires_in")) + .refreshToken(MapUtils.getString(map, "refresh_token")) + .build(); + } + + /** + * Utility method to map from Map to AuthUser + * @param map Object + * @return AuthUser + */ + public static AuthUser mapToAuthUser(Map map) { + return AuthUser.builder() + .uid(MapUtils.getString(map, "sub")) + .username(MapUtils.getString(map, "email")) + .rawUserInfo(map) + .build(); + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java index 75b5bec8d..eb23baea0 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java @@ -118,10 +118,7 @@ public Mono extendValidity(String token) { private Duration getTokenExpireTime() { long maxAgeInSeconds = commonConfig.getCookie().getMaxAgeInSeconds(); - if (maxAgeInSeconds >= 0) { - return Duration.ofSeconds(maxAgeInSeconds).plus(Duration.ofDays(1)); - } - return Duration.ofDays(7); + return Duration.ofSeconds(maxAgeInSeconds); } @Override diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml index 1a1ce9e51..2c2c1293e 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application.yaml +++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml @@ -84,7 +84,7 @@ common: marketplace: private-mode: ${LOWCODER_MARKETPLACE_PRIVATE_MODE:true} lowcoder-public-url: ${LOWCODER_PUBLIC_URL:http://localhost:8080} - notifications-email-sender: ${LOWCODER_LOST_PASSWORD_EMAIL_SENDER:info@lowcoder.org} + notifications-email-sender: ${LOWCODER_EMAIL_NOTIFICATIONS_SENDER:info@localhost} material: mongodb-grid-fs: