From 78cc381719fe14224f3f0d0c07541147e3a5a14d Mon Sep 17 00:00:00 2001 From: chengluo Date: Sun, 29 Jan 2023 17:16:00 +0800 Subject: [PATCH 1/2] feat: move workspace & request buffer limit settings to application.yml & check org state in enterprise mode --- .../repository/OrganizationRepository.java | 1 + .../service/OrganizationServiceImpl.java | 30 ++++++++++++++----- .../openblocks/sdk/exception/BizError.java | 1 + .../src/main/resources/locale_en.properties | 3 +- .../framework/security/SecurityConfig.java | 2 ++ .../selfhost/ce/application-selfhost.yml | 6 +--- .../resources/selfhost/ce/application.yml | 4 +++ 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/repository/OrganizationRepository.java b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/repository/OrganizationRepository.java index e7297365..9b3602df 100644 --- a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/repository/OrganizationRepository.java +++ b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/repository/OrganizationRepository.java @@ -13,6 +13,7 @@ @Repository public interface OrganizationRepository extends ReactiveMongoRepository { + Mono findFirstByStateMatches(OrganizationState state); Flux findByIdInAndState(Collection id, OrganizationState state); diff --git a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/service/OrganizationServiceImpl.java b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/service/OrganizationServiceImpl.java index d9a0f04a..a70c9d60 100644 --- a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/service/OrganizationServiceImpl.java +++ b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/organization/service/OrganizationServiceImpl.java @@ -1,15 +1,19 @@ package com.openblocks.domain.organization.service; import static com.openblocks.domain.organization.model.OrganizationState.ACTIVE; +import static com.openblocks.domain.organization.model.OrganizationState.DELETED; import static com.openblocks.domain.util.QueryDslUtils.fieldName; import static com.openblocks.sdk.exception.BizError.UNABLE_TO_FIND_VALID_ORG; import static com.openblocks.sdk.util.ExceptionUtils.deferredError; +import static com.openblocks.sdk.util.ExceptionUtils.ofError; import static com.openblocks.sdk.util.LocaleUtils.getLocale; import static com.openblocks.sdk.util.LocaleUtils.getMessage; import java.util.Collection; import java.util.Locale; +import javax.annotation.Nonnull; + import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -114,14 +118,24 @@ public Mono getOrganizationInEnterpriseMode() { if (commonConfig.getWorkspace().getMode() == WorkspaceMode.SAAS) { return Mono.empty(); } - return Mono.defer(() -> { - String enterpriseOrgId = commonConfig.getWorkspace().getEnterpriseOrgId(); - if (StringUtils.isNotBlank(enterpriseOrgId)) { - return repository.findById(enterpriseOrgId); - } - return Mono.empty(); - }) - .switchIfEmpty(repository.findAll().next()); + return getByEnterpriseOrgId() + .switchIfEmpty(repository.findFirstByStateMatches(ACTIVE)); + } + + @Nonnull + private Mono getByEnterpriseOrgId() { + String enterpriseOrgId = commonConfig.getWorkspace().getEnterpriseOrgId(); + if (StringUtils.isBlank(enterpriseOrgId)) { + return Mono.empty(); + } + return repository.findById(enterpriseOrgId) + .delayUntil(org -> { + if (org.getState() == DELETED) { + return ofError(BizError.ORG_DELETED_FOR_ENTERPRISE_MODE, "ORG_DELETED_FOR_ENTERPRISE_MODE"); + } + return Mono.empty(); + } + ); } @Override diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/exception/BizError.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/exception/BizError.java index dc4c5793..df4964d3 100644 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/exception/BizError.java +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/exception/BizError.java @@ -31,6 +31,7 @@ public enum BizError { EXCEED_MAX_ORG_MEMBER_COUNT(500, 5104), UNABLE_TO_FIND_VALID_ORG(500, 5105), EXCEED_MAX_DEVELOPER_COUNT(500, 5106), + ORG_DELETED_FOR_ENTERPRISE_MODE(500, 5107), // GROUP related, code range 5150 - 5199 INVALID_GROUP_ID(500, 5150), diff --git a/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties b/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties index e78267aa..1054c1d7 100644 --- a/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties +++ b/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties @@ -269,4 +269,5 @@ GOOGLESHEETS_REQUEST_ERROR=Google Sheets request failed. GOOGLESHEETS_DATASOURCE_CONFIG_ERROR=Fail to parse Google Sheets data source configuration. GOOGLESHEETS_EMPTY_ROW=No data found at this row index. Do you want to try inserting something first? APPLICATION_EDIT_ERROR_LACK_OF_DATASOURCE_PERMISSIONS=Current changes of this application will not be saved for lacking of some data sources'' permissions. -CERTIFICATE_EMPTY=Certificate is empty. \ No newline at end of file +CERTIFICATE_EMPTY=Certificate is empty. +ORG_DELETED_FOR_ENTERPRISE_MODE=Provided enterpriseOrgId workspace has been deleted, please contact Openblocks team. \ No newline at end of file diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java index a1026386..85f3e318 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java @@ -82,6 +82,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, INVITATION_URL + "/**"), // invitation ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, CUSTOM_AUTH + "/logout"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.HEAD, STATE_URL + "/healthCheck"), + + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/api/test"), // used in public viewed apps ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL), // system config ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL + "/deploymentId"), // system config diff --git a/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application-selfhost.yml b/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application-selfhost.yml index 053ed187..291ab68e 100644 --- a/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application-selfhost.yml +++ b/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application-selfhost.yml @@ -4,8 +4,6 @@ common: salt: ${ENCRYPTION_SALT:openblocks.dev} security: corsAllowedDomainString: ${CORS_ALLOWED_DOMAINS:*} - max-query-request-size-in-mb: 20 - max-query-response-size-in-mb: 20 workspace: mode: ENTERPRISE @@ -14,6 +12,4 @@ spring: mongodb: uri: ${MONGODB_URI:mongodb://localhost:27017/openblocks?socketTimeoutMS=5000} redis: - url: ${REDIS_URL:redis://localhost:6379} - codec: - max-in-memory-size: 20MB + url: ${REDIS_URL:redis://localhost:6379} \ No newline at end of file diff --git a/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application.yml b/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application.yml index 210011e9..941fb702 100644 --- a/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application.yml +++ b/server/api-service/openblocks-server/src/main/resources/selfhost/ce/application.yml @@ -11,6 +11,8 @@ spring: main: allow-bean-definition-overriding: true allow-circular-references: true + codec: + max-in-memory-size: 20MB server: compression: @@ -31,6 +33,8 @@ common: block-hound-enable: false js-executor: host: ${JS_EXECUTOR_URI:http://127.0.0.1:6060} + max-query-request-size-in-mb: 20 + max-query-response-size-in-mb: 20 material: mongodb-grid-fs: From 30ee8e8c66d073f53bd436824f98d39eafe36a34 Mon Sep 17 00:00:00 2001 From: chengluo Date: Tue, 31 Jan 2023 20:03:57 +0800 Subject: [PATCH 2/2] feat: enable reset password by workspace admins --- .../domain/user/service/UserService.java | 4 ++- .../domain/user/service/UserServiceImpl.java | 30 ++++++++++++++++++- .../openblocks/sdk/config/CommonConfig.java | 4 +++ .../src/main/resources/locale_en.properties | 3 +- .../framework/security/SecurityConfig.java | 2 -- .../api/usermanagement/UserApiService.java | 10 +++++-- .../api/usermanagement/UserController.java | 23 ++++++++++++-- 7 files changed, 66 insertions(+), 10 deletions(-) diff --git a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserService.java b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserService.java index 37918f46..484ac2db 100644 --- a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserService.java +++ b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserService.java @@ -8,8 +8,8 @@ import com.openblocks.domain.user.model.AuthorizedUser; import com.openblocks.domain.user.model.Connection; -import com.openblocks.domain.user.model.UserDetail; import com.openblocks.domain.user.model.User; +import com.openblocks.domain.user.model.UserDetail; import com.openblocks.infra.annotation.NonEmptyMono; import reactor.core.publisher.Mono; @@ -45,6 +45,8 @@ public interface UserService { Mono updatePassword(String userId, String oldPassword, String newPassword); + Mono resetPassword(String userId); + Mono setPassword(String userId, String password); Mono buildUserDetail(User user, boolean withoutDynamicGroups); diff --git a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserServiceImpl.java b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserServiceImpl.java index 9279df44..25aee17c 100644 --- a/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserServiceImpl.java +++ b/server/api-service/openblocks-domain/src/main/java/com/openblocks/domain/user/service/UserServiceImpl.java @@ -7,6 +7,7 @@ import static com.openblocks.sdk.util.ExceptionUtils.ofError; import static com.openblocks.sdk.util.ExceptionUtils.ofException; +import java.security.SecureRandom; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -17,9 +18,11 @@ import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.codec.multipart.Part; @@ -37,9 +40,9 @@ import com.openblocks.domain.organization.service.OrgMemberService; import com.openblocks.domain.user.model.AuthorizedUser; import com.openblocks.domain.user.model.Connection; -import com.openblocks.domain.user.model.UserDetail; import com.openblocks.domain.user.model.User; import com.openblocks.domain.user.model.User.TransformedUserInfo; +import com.openblocks.domain.user.model.UserDetail; import com.openblocks.domain.user.model.UserState; import com.openblocks.domain.user.repository.UserRepository; import com.openblocks.infra.mongo.MongoUpsertHelper; @@ -245,6 +248,31 @@ public Mono updatePassword(String userId, String oldPassword, String ne .thenReturn(true); } + @Override + public Mono resetPassword(String userId) { + return findById(userId) + .flatMap(user -> { + String password = user.getPassword(); + if (StringUtils.isBlank(password)) { + return ofError(BizError.INVALID_PASSWORD, "PASSWORD_NOT_SET_YET"); + } + + String randomStr = generateNewRandomPwd(); + user.setPassword(encryptionService.encryptPassword(randomStr)); + return repository.save(user) + .thenReturn(randomStr); + }); + } + + @SuppressWarnings("SpellCheckingInspection") + @Nonnull + private static String generateNewRandomPwd() { + char[] possibleCharacters = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}<>?") + .toCharArray(); + return RandomStringUtils.random(12, 0, possibleCharacters.length - 1, + false, false, possibleCharacters, new SecureRandom()); + } + @Override public Mono setPassword(String userId, String password) { return findById(userId) diff --git a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java index 2a081aa3..aa5f8399 100644 --- a/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java +++ b/server/api-service/openblocks-sdk/src/main/java/com/openblocks/sdk/config/CommonConfig.java @@ -39,6 +39,10 @@ public boolean isSelfHost() { return !isCloud(); } + public boolean isEnterpriseMode() { + return workspace.getMode() == WorkspaceMode.ENTERPRISE; + } + @Data public static class Domain { private String defaultValue; diff --git a/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties b/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties index 1054c1d7..aacb3d75 100644 --- a/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties +++ b/server/api-service/openblocks-sdk/src/main/resources/locale_en.properties @@ -43,7 +43,8 @@ USER_NOT_SIGNED_IN=Unknown user, you have to log in first. FAIL_TO_GET_OIDC_INFO=Failed to get OIDC information, error message: {0}. LOG_IN_SOURCE_NOT_SUPPORTED=Sorry, this log in channel is not supported. USER_LOGIN_ID_EXIST=Current email already used by another user. -INVALID_PASSWORD=Sorry, passwords do not match, please retype. +INVALID_PASSWORD=Sorry, passwords do not match. +PASSWORD_NOT_SET_YET=This user hasn't set password yet and cannot be reset. INVALID_EMAIL_OR_PASSWORD=Invalid email or password. ALREADY_BIND=Sorry, {0} has been bound by user {1}. NEED_BIND_THIRD_PARTY_CONNECTION=Sorry, it needs to bind the current workspace login channel. diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java index 85f3e318..a1026386 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/framework/security/SecurityConfig.java @@ -82,8 +82,6 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, INVITATION_URL + "/**"), // invitation ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, CUSTOM_AUTH + "/logout"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.HEAD, STATE_URL + "/healthCheck"), - - ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/api/test"), // used in public viewed apps ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL), // system config ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL + "/deploymentId"), // system config diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserApiService.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserApiService.java index 31c09bf5..0147084b 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserApiService.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserApiService.java @@ -28,13 +28,13 @@ public class UserApiService { private UserService userService; public Mono getUserDetailById(String userId) { - return checkPermission(userId) + return checkAdminPermissionAndUserBelongsToCurrentOrg(userId) .then(userService.findById(userId) .flatMap(user -> userService.buildUserDetail(user, false))); } - private Mono checkPermission(String userId) { - return sessionUserService.getVisitorOrgMember() + private Mono checkAdminPermissionAndUserBelongsToCurrentOrg(String userId) { + return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { if (!orgMember.isAdmin()) { return ofError(UNSUPPORTED_OPERATION, "BAD_REQUEST"); @@ -50,4 +50,8 @@ private Mono checkPermission(String userId) { }); } + public Mono resetPassword(String userId) { + return checkAdminPermissionAndUserBelongsToCurrentOrg(userId) + .then(userService.resetPassword(userId)); + } } diff --git a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserController.java b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserController.java index e4e45d2b..d33a9f62 100644 --- a/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserController.java +++ b/server/api-service/openblocks-server/src/main/java/com/openblocks/api/usermanagement/UserController.java @@ -32,6 +32,7 @@ import com.openblocks.domain.user.service.UserStatusService; import com.openblocks.infra.constant.NewUrl; import com.openblocks.infra.constant.Url; +import com.openblocks.sdk.config.CommonConfig; import com.openblocks.sdk.exception.BizError; import com.openblocks.sdk.util.UriUtils; @@ -61,6 +62,9 @@ public class UserController { @Autowired private UserApiService userApiService; + @Autowired + private CommonConfig commonConfig; + @GetMapping("/me") public Mono> getUserProfile(ServerWebExchange exchange) { String domain = UriUtils.getRefererDomain(exchange); @@ -133,11 +137,23 @@ public Mono getProfilePhoto(ServerWebExchange exchange, @PathVariable Stri @PutMapping("/password") public Mono> updatePassword(@RequestBody UpdatePasswordRequest request) { - if (StringUtils.isBlank(request.oldPassword) || StringUtils.isBlank(request.newPassword)) { + if (StringUtils.isBlank(request.oldPassword()) || StringUtils.isBlank(request.newPassword())) { return ofError(BizError.INVALID_PARAMETER, "PASSWORD_EMPTY"); } return sessionUserService.getVisitorId() - .flatMap(user -> userService.updatePassword(user, request.oldPassword, request.newPassword)) + .flatMap(user -> userService.updatePassword(user, request.oldPassword(), request.newPassword())) + .map(ResponseView::success); + } + + @PostMapping("/reset-password") + public Mono> resetPassword(@RequestBody ResetPasswordRequest request) { + if (!commonConfig.isEnterpriseMode()) { + return ofError(BizError.UNSUPPORTED_OPERATION, "BAD_REQUEST"); + } + if (StringUtils.isBlank(request.userId())) { + return ofError(BizError.INVALID_PARAMETER, "INVALID_USER_ID"); + } + return userApiService.resetPassword(request.userId()) .map(ResponseView::success); } @@ -165,6 +181,9 @@ public Mono> getUserDetail(@PathVariable("id") String userId) { .map(ResponseView::success); } + public record ResetPasswordRequest(String userId) { + } + public record UpdatePasswordRequest(String oldPassword, String newPassword) { }