diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/auth/ConsumerPermissionValidator.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/auth/ConsumerPermissionValidator.java index 49f06a20335..e7641012d48 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/auth/ConsumerPermissionValidator.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/auth/ConsumerPermissionValidator.java @@ -94,6 +94,18 @@ public boolean hasManageAppMasterPermission(String appId) { throw new UnsupportedOperationException("Not supported operation"); } + @Override + public boolean hasCreateUserPermission() { + long consumerId = consumerAuthUtil.retrieveConsumerIdFromCtx(); + return permissionService.consumerHasPermission(consumerId, PermissionType.CREATE_USER, + SYSTEM_PERMISSION_TARGET_ID); + } + + @Override + public boolean hasCreateUserPermission(String userId) { + return false; + } + @Override protected boolean hasPermissions(List requiredPerms) { if (requiredPerms == null || requiredPerms.isEmpty()) { diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/dto/OpenUserDTO.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/dto/OpenUserDTO.java new file mode 100644 index 00000000000..32b3e4aca8a --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/dto/OpenUserDTO.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.openapi.dto; + +/** + * Open API User DTO for user management operations. + * + * @author dreamweaver + */ +public class OpenUserDTO { + + private String username; + private String userDisplayName; + private String password; + private String email; + private Integer enabled; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUserDisplayName() { + return userDisplayName; + } + + public void setUserDisplayName(String userDisplayName) { + this.userDisplayName = userDisplayName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Integer getEnabled() { + return enabled; + } + + public void setEnabled(Integer enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "OpenUserDTO{" + "username='" + username + '\'' + ", userDisplayName='" + userDisplayName + + '\'' + ", email='" + email + '\'' + ", enabled=" + enabled + '}'; + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java index a1d17d75872..7465632849c 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java @@ -19,6 +19,7 @@ import static com.ctrip.framework.apollo.portal.service.SystemRoleManagerService.CREATE_APPLICATION_ROLE_NAME; import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.portal.service.SystemRoleManagerService; import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.openapi.entity.Consumer; import com.ctrip.framework.apollo.openapi.entity.ConsumerAudit; @@ -206,7 +207,7 @@ public List assignNamespaceRoleToConsumer(String token, String app } private ConsumerInfo convert(Consumer consumer, String token, boolean allowCreateApplication, - Integer rateLimit) { + boolean allowCreateUser, Integer rateLimit) { ConsumerInfo consumerInfo = new ConsumerInfo(); consumerInfo.setConsumerId(consumer.getId()); consumerInfo.setAppId(consumer.getAppId()); @@ -219,6 +220,7 @@ private ConsumerInfo convert(Consumer consumer, String token, boolean allowCreat consumerInfo.setToken(token); consumerInfo.setAllowCreateApplication(allowCreateApplication); + consumerInfo.setAllowCreateUser(allowCreateUser); return consumerInfo; } @@ -232,13 +234,17 @@ public ConsumerInfo getConsumerInfoByAppId(String appId) { return null; } return convert(consumer, consumerToken.getToken(), isAllowCreateApplication(consumer.getId()), - getRateLimit(consumer.getId())); + isAllowCreateUser(consumer.getId()), getRateLimit(consumer.getId())); } private boolean isAllowCreateApplication(Long consumerId) { return isAllowCreateApplication(Collections.singletonList(consumerId)).get(0); } + private boolean isAllowCreateUser(Long consumerId) { + return isAllowCreateUser(Collections.singletonList(consumerId)).get(0); + } + private Integer getRateLimit(Long consumerId) { List list = getRateLimit(Collections.singletonList(consumerId)); if (CollectionUtils.isEmpty(list)) { @@ -268,6 +274,27 @@ private List isAllowCreateApplication(List consumerIdList) { return list; } + private List isAllowCreateUser(List consumerIdList) { + Role createUserRole = getCreateUserRole(); + if (createUserRole == null) { + List list = new ArrayList<>(consumerIdList.size()); + for (Long ignored : consumerIdList) { + list.add(false); + } + return list; + } + + long roleId = createUserRole.getId(); + List list = new ArrayList<>(consumerIdList.size()); + for (Long consumerId : consumerIdList) { + ConsumerRole createUserConsumerRole = + consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, roleId); + list.add(createUserConsumerRole != null); + } + + return list; + } + private List getRateLimit(List consumerIds) { List consumerTokens = consumerTokenRepository.findByConsumerIdIn(consumerIds); Map consumerRateLimits = consumerTokens.stream().collect(Collectors.toMap( @@ -282,6 +309,10 @@ private Role getCreateAppRole() { return rolePermissionService.findRoleByRoleName(CREATE_APPLICATION_ROLE_NAME); } + private Role getCreateUserRole() { + return rolePermissionService.findRoleByRoleName(SystemRoleManagerService.CREATE_USER_ROLE_NAME); + } + public ConsumerRole assignCreateApplicationRoleToConsumer(String token) { Long consumerId = getConsumerIdByToken(token); if (consumerId == null) { @@ -304,6 +335,28 @@ public ConsumerRole assignCreateApplicationRoleToConsumer(String token) { return consumerRoleRepository.save(consumerRole); } + public ConsumerRole assignCreateUserRoleToConsumer(String token) { + Long consumerId = getConsumerIdByToken(token); + if (consumerId == null) { + throw new BadRequestException("Token is Illegal"); + } + Role createUserRole = getCreateUserRole(); + if (createUserRole == null) { + throw NotFoundException.roleNotFound(SystemRoleManagerService.CREATE_USER_ROLE_NAME); + } + + long roleId = createUserRole.getId(); + ConsumerRole createUserConsumerRole = + consumerRoleRepository.findByConsumerIdAndRoleId(consumerId, roleId); + if (createUserConsumerRole != null) { + return createUserConsumerRole; + } + + String operator = userInfoHolder.getUser().getUserId(); + ConsumerRole consumerRole = createConsumerRole(consumerId, roleId, operator); + return consumerRoleRepository.save(consumerRole); + } + @Transactional public ConsumerRole assignAppRoleToConsumer(String token, String appId) { @@ -436,6 +489,7 @@ public List findConsumerInfoList(Pageable page) { List consumerIdList = consumerList.stream().map(Consumer::getId).collect(Collectors.toList()); List allowCreateApplicationList = isAllowCreateApplication(consumerIdList); + List allowCreateUserList = isAllowCreateUser(consumerIdList); List rateLimitList = getRateLimit(consumerIdList); List consumerInfoList = new ArrayList<>(consumerList.size()); @@ -443,8 +497,8 @@ public List findConsumerInfoList(Pageable page) { for (int i = 0; i < consumerList.size(); i++) { Consumer consumer = consumerList.get(i); // without token - ConsumerInfo consumerInfo = - convert(consumer, null, allowCreateApplicationList.get(i), rateLimitList.get(i)); + ConsumerInfo consumerInfo = convert(consumer, null, allowCreateApplicationList.get(i), + allowCreateUserList.get(i), rateLimitList.get(i)); consumerInfoList.add(consumerInfo); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/UserController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/UserController.java new file mode 100644 index 00000000000..bc46572e5f8 --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/UserController.java @@ -0,0 +1,187 @@ +/* + * Copyright 2025 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.openapi.v1.controller; + +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.core.utils.StringUtils; +import com.ctrip.framework.apollo.openapi.api.UserManagementApi; +import com.ctrip.framework.apollo.openapi.model.OpenUserDTO; +import com.ctrip.framework.apollo.openapi.model.UserInfo; +import com.ctrip.framework.apollo.portal.entity.po.UserPO; +import com.ctrip.framework.apollo.portal.spi.UserService; +import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService; +import com.ctrip.framework.apollo.portal.util.checker.AuthUserPasswordChecker; +import com.ctrip.framework.apollo.portal.util.checker.CheckResult; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * OpenAPI User Management Controller Provides RESTful APIs for user management operations through + * OpenAPI + * + * @author dreamweaver + */ +@RestController("openapiUserController") +@RequestMapping("/openapi/v1") +public class UserController implements UserManagementApi { + + private static final int DEFAULT_USER_ENABLED = 1; + + private final UserService userService; + private final AuthUserPasswordChecker passwordChecker; + + public UserController(final UserService userService, + final AuthUserPasswordChecker passwordChecker) { + this.userService = userService; + this.passwordChecker = passwordChecker; + } + + /** + * Convert portal UserInfo BO to generated model UserInfo + */ + private UserInfo toModelUserInfo(com.ctrip.framework.apollo.portal.entity.bo.UserInfo bo) { + if (bo == null) { + return null; + } + UserInfo model = new UserInfo(); + model.setUserId(bo.getUserId()); + model.setName(bo.getName()); + model.setEmail(bo.getEmail()); + model.setEnabled(UserInfo.EnabledEnum.fromValue(bo.getEnabled())); + return model; + } + + /** + * Create a new user + * + * @param openUserDTO user information to create + * @return ResponseEntity with created user information + */ + @PreAuthorize(value = "@unifiedPermissionValidator.hasCreateUserPermission()") + @ApolloAuditLog(name = "OpenAPI.createUser", type = OpType.CREATE, + description = "Create user via OpenAPI") + @PostMapping("/users") + @Override + public ResponseEntity createUser(@RequestBody OpenUserDTO openUserDTO) { + // Validate required fields + if (StringUtils.isContainEmpty(openUserDTO.getUsername(), openUserDTO.getPassword())) { + throw new BadRequestException("Username and password cannot be empty."); + } + + if (StringUtils.isEmpty(openUserDTO.getEmail())) { + throw new BadRequestException("Email cannot be empty."); + } + + // Check password strength + CheckResult pwdCheckRes = passwordChecker.checkWeakPassword(openUserDTO.getPassword()); + if (!pwdCheckRes.isSuccess()) { + throw new BadRequestException(pwdCheckRes.getMessage()); + } + + // Check if UserService supports user creation + if (!(userService instanceof SpringSecurityUserService)) { + throw new UnsupportedOperationException( + "Create user operation is not supported with current user service implementation"); + } + + // Convert DTO to PO and set defaults + UserPO userPO = new UserPO(); + userPO.setUsername(openUserDTO.getUsername()); + userPO.setPassword(openUserDTO.getPassword()); + userPO.setEmail(openUserDTO.getEmail()); + userPO.setUserDisplayName( + openUserDTO.getUserDisplayName() != null ? openUserDTO.getUserDisplayName() + : openUserDTO.getUsername()); + int enabledVal = DEFAULT_USER_ENABLED; + if (openUserDTO.getEnabled() != null) { + enabledVal = openUserDTO.getEnabled().getValue(); + } + userPO.setEnabled(enabledVal); + + // Create user + ((SpringSecurityUserService) userService).create(userPO); + + // Retrieve and return the created user information + com.ctrip.framework.apollo.portal.entity.bo.UserInfo createdUser = + userService.findByUserId(openUserDTO.getUsername()); + return ResponseEntity.ok(toModelUserInfo(createdUser)); + } + + /** + * Get user information by user ID + * + * @param userId the user ID to query + * @return UserInfo object + */ + @PreAuthorize(value = "@unifiedPermissionValidator.hasCreateUserPermission()") + @GetMapping("/users/{userId}") + @Override + public ResponseEntity getUserByUserId(@PathVariable String userId) { + com.ctrip.framework.apollo.portal.entity.bo.UserInfo userInfo = + userService.findByUserId(userId); + if (userInfo == null) { + throw new BadRequestException("User not found: " + userId); + } + return ResponseEntity.ok(toModelUserInfo(userInfo)); + } + + /** + * Search users by keyword + * + * @param keyword keyword to search (searches in username and display name) + * @param includeInactiveUsers whether to include inactive users + * @param offset pagination offset + * @param limit pagination limit + * @return list of UserInfo objects + */ + @PreAuthorize(value = "@unifiedPermissionValidator.hasCreateUserPermission()") + @GetMapping("/users") + @Override + public ResponseEntity> searchUsers( + @RequestParam(value = "keyword", required = false, defaultValue = "") String keyword, + @RequestParam(value = "includeInactiveUsers", + defaultValue = "false") Boolean includeInactiveUsers, + @RequestParam(value = "offset", defaultValue = "0") Integer offset, + @RequestParam(value = "limit", defaultValue = "10") Integer limit) { + + if (limit <= 0 || limit > 100) { + throw new BadRequestException("Limit must be between 1 and 100"); + } + + if (offset < 0) { + throw new BadRequestException("Offset must be non-negative"); + } + + List users = userService.searchUsers(keyword, offset, limit, includeInactiveUsers) + .stream() + .map(this::toModelUserInfo) + .collect(Collectors.toList()); + return ResponseEntity.ok(users); + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PermissionValidator.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PermissionValidator.java index 00ff87b773c..133e4484611 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PermissionValidator.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PermissionValidator.java @@ -58,4 +58,8 @@ boolean shouldHideConfigToCurrentUser(String appId, String env, String clusterNa boolean hasCreateApplicationPermission(String userId); boolean hasManageAppMasterPermission(String appId); + + boolean hasCreateUserPermission(); + + boolean hasCreateUserPermission(String userId); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UnifiedPermissionValidator.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UnifiedPermissionValidator.java index 28b3e157339..49dc22f730d 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UnifiedPermissionValidator.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UnifiedPermissionValidator.java @@ -106,4 +106,14 @@ public boolean hasDeleteNamespacePermission(String appId) { public boolean hasManageAppMasterPermission(String appId) { return getDelegate().hasManageAppMasterPermission(appId); } + + @Override + public boolean hasCreateUserPermission() { + return getDelegate().hasCreateUserPermission(); + } + + @Override + public boolean hasCreateUserPermission(String userId) { + return getDelegate().hasCreateUserPermission(userId); + } } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UserPermissionValidator.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UserPermissionValidator.java index 7266f1bd11b..0aceaf2136c 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UserPermissionValidator.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/UserPermissionValidator.java @@ -107,6 +107,16 @@ public boolean hasManageAppMasterPermission(String appId) { .hasManageAppMasterPermission(userInfoHolder.getUser().getUserId(), appId)); } + @Override + public boolean hasCreateUserPermission() { + return systemRoleManagerService.hasCreateUserPermission(userInfoHolder.getUser().getUserId()); + } + + @Override + public boolean hasCreateUserPermission(String userId) { + return systemRoleManagerService.hasCreateUserPermission(userId); + } + @Override protected boolean hasPermissions(List requiredPerms) { if (requiredPerms == null || requiredPerms.isEmpty()) { diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java index eb0b1082e40..f603587fa73 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java @@ -317,6 +317,10 @@ public boolean isManageAppMasterPermissionEnabled() { return getBooleanProperty(SystemRoleManagerService.MANAGE_APP_MASTER_LIMIT_SWITCH_KEY, false); } + public boolean isCreateUserPermissionEnabled() { + return getBooleanProperty(SystemRoleManagerService.CREATE_USER_LIMIT_SWITCH_KEY, false); + } + public String getAdminServiceAccessTokens() { return getValue("admin-service.access.tokens"); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/constant/PermissionType.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/constant/PermissionType.java index a38e2f92b4c..b4d6aae0bc5 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/constant/PermissionType.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/constant/PermissionType.java @@ -23,6 +23,7 @@ public interface PermissionType { */ String CREATE_APPLICATION = "CreateApplication"; String MANAGE_APP_MASTER = "ManageAppMaster"; + String CREATE_USER = "CreateUser"; /** * APP level permission diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java index d273ebf5415..3972b64b0f3 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java @@ -99,6 +99,9 @@ public ConsumerInfo create(@RequestBody ConsumerCreateRequestVO requestVO, if (requestVO.isAllowCreateApplication()) { consumerService.assignCreateApplicationRoleToConsumer(consumerToken.getToken()); } + if (requestVO.isAllowCreateUser()) { + consumerService.assignCreateUserRoleToConsumer(consumerToken.getToken()); + } return consumerService.getConsumerInfoByAppId(requestVO.getAppId()); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java index 36aefac198c..e521d85cac4 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java @@ -480,4 +480,49 @@ public JsonObject isManageAppMasterPermissionEnabled() { systemRoleManagerService.isManageAppMasterPermissionEnabled()); return rs; } + + @PreAuthorize(value = "@unifiedPermissionValidator.isSuperAdmin()") + @PostMapping("/system/role/createUser") + @ApolloAuditLog(type = OpType.CREATE, name = "Auth.addCreateUserRoleToUser") + public ResponseEntity addCreateUserRoleToUser(@RequestBody List userIds) { + userIds.forEach(this::checkUserExists); + rolePermissionService.assignRoleToUsers(SystemRoleManagerService.CREATE_USER_ROLE_NAME, + new HashSet<>(userIds), userInfoHolder.getUser().getUserId()); + return ResponseEntity.ok().build(); + } + + @PreAuthorize(value = "@unifiedPermissionValidator.isSuperAdmin()") + @DeleteMapping("/system/role/createUser/{userId}") + @ApolloAuditLog(type = OpType.DELETE, name = "Auth.deleteCreateUserRoleFromUser") + public ResponseEntity deleteCreateUserRoleFromUser(@PathVariable("userId") String userId) { + checkUserExists(userId); + Set userIds = new HashSet<>(); + userIds.add(userId); + rolePermissionService.removeRoleFromUsers(SystemRoleManagerService.CREATE_USER_ROLE_NAME, + userIds, userInfoHolder.getUser().getUserId()); + return ResponseEntity.ok().build(); + } + + @PreAuthorize(value = "@unifiedPermissionValidator.isSuperAdmin()") + @GetMapping("/system/role/createUser") + public List getCreateUserRoleUsers() { + return rolePermissionService.queryUsersWithRole(SystemRoleManagerService.CREATE_USER_ROLE_NAME) + .stream().map(UserInfo::getUserId).collect(Collectors.toList()); + } + + @GetMapping("/system/role/createUser/{userId}") + public JsonObject hasCreateUserPermission(@PathVariable String userId) { + JsonObject rs = new JsonObject(); + rs.addProperty("hasCreateUserPermission", + unifiedPermissionValidator.hasCreateUserPermission(userId)); + return rs; + } + + @GetMapping("/system/role/createUser/enabled") + public JsonObject isCreateUserPermissionEnabled() { + JsonObject rs = new JsonObject(); + rs.addProperty("isCreateUserPermissionEnabled", + systemRoleManagerService.isCreateUserPermissionEnabled()); + return rs; + } } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java index ca7e875f2b8..6f8e9c3696a 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java @@ -20,8 +20,10 @@ * @see com.ctrip.framework.apollo.openapi.entity.Consumer */ public class ConsumerCreateRequestVO { + private String appId; private boolean allowCreateApplication; + private boolean allowCreateUser; private String name; private String orgId; private String orgName; @@ -45,6 +47,14 @@ public void setAllowCreateApplication(boolean allowCreateApplication) { this.allowCreateApplication = allowCreateApplication; } + public boolean isAllowCreateUser() { + return allowCreateUser; + } + + public void setAllowCreateUser(boolean allowCreateUser) { + this.allowCreateUser = allowCreateUser; + } + public String getName() { return name; } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java index 64ea36d7112..477d7ea5363 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java @@ -21,6 +21,7 @@ * @see com.ctrip.framework.apollo.openapi.entity.ConsumerRole */ public class ConsumerInfo { + private String appId; private String name; @@ -32,6 +33,7 @@ public class ConsumerInfo { private long consumerId; private String token; private boolean allowCreateApplication; + private boolean allowCreateUser; private Integer rateLimit; @@ -107,6 +109,14 @@ public void setAllowCreateApplication(boolean allowCreateApplication) { this.allowCreateApplication = allowCreateApplication; } + public boolean isAllowCreateUser() { + return allowCreateUser; + } + + public void setAllowCreateUser(boolean allowCreateUser) { + this.allowCreateUser = allowCreateUser; + } + public Integer getRateLimit() { return rateLimit; } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/RoleInitializationService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/RoleInitializationService.java index 44e4527235c..c91e6abf468 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/RoleInitializationService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/RoleInitializationService.java @@ -31,6 +31,8 @@ void initNamespaceSpecificEnvRoles(String appId, String namespaceName, String en void initCreateAppRole(); + void initCreateUserRole(); + void initManageAppMasterRole(String appId, String operator); void initClusterNamespaceRoles(String appId, String env, String clusterName, String operator); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/SystemRoleManagerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/SystemRoleManagerService.java index c6eab7a3378..b642ffda0c4 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/SystemRoleManagerService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/SystemRoleManagerService.java @@ -26,6 +26,7 @@ @Service public class SystemRoleManagerService { + public static final Logger logger = LoggerFactory.getLogger(SystemRoleManagerService.class); public static final String SYSTEM_PERMISSION_TARGET_ID = "SystemRole"; @@ -34,9 +35,13 @@ public class SystemRoleManagerService { RoleUtils.buildCreateApplicationRoleName(PermissionType.CREATE_APPLICATION, SYSTEM_PERMISSION_TARGET_ID); + public static final String CREATE_USER_ROLE_NAME = RoleUtils + .buildCreateApplicationRoleName(PermissionType.CREATE_USER, SYSTEM_PERMISSION_TARGET_ID); + public static final String CREATE_APPLICATION_LIMIT_SWITCH_KEY = "role.create-application.enabled"; public static final String MANAGE_APP_MASTER_LIMIT_SWITCH_KEY = "role.manage-app-master.enabled"; + public static final String CREATE_USER_LIMIT_SWITCH_KEY = "role.create-user.enabled"; private final RolePermissionService rolePermissionService; @@ -54,6 +59,7 @@ public SystemRoleManagerService(final RolePermissionService rolePermissionServic @PostConstruct private void init() { roleInitializationService.initCreateAppRole(); + roleInitializationService.initCreateUserRole(); } private boolean isCreateApplicationPermissionEnabled() { @@ -64,6 +70,10 @@ public boolean isManageAppMasterPermissionEnabled() { return portalConfig.isManageAppMasterPermissionEnabled(); } + public boolean isCreateUserPermissionEnabled() { + return portalConfig.isCreateUserPermissionEnabled(); + } + public boolean hasCreateApplicationPermission(String userId) { if (!isCreateApplicationPermissionEnabled()) { return true; @@ -80,4 +90,13 @@ public boolean hasManageAppMasterPermission(String userId, String appId) { return rolePermissionService.userHasPermission(userId, PermissionType.MANAGE_APP_MASTER, appId); } + + public boolean hasCreateUserPermission(String userId) { + if (!isCreateUserPermissionEnabled()) { + return true; + } + + return rolePermissionService.userHasPermission(userId, PermissionType.CREATE_USER, + SYSTEM_PERMISSION_TARGET_ID); + } } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java index 3bcffa70e2d..570ad1bcad9 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java @@ -157,6 +157,27 @@ public void initCreateAppRole() { Sets.newHashSet(createAppPermission.getId())); } + @Transactional + @Override + public void initCreateUserRole() { + if (rolePermissionService + .findRoleByRoleName(SystemRoleManagerService.CREATE_USER_ROLE_NAME) != null) { + return; + } + Permission createUserPermission = permissionRepository.findTopByPermissionTypeAndTargetId( + PermissionType.CREATE_USER, SystemRoleManagerService.SYSTEM_PERMISSION_TARGET_ID); + if (createUserPermission == null) { + // create user permission init + createUserPermission = createPermission(SystemRoleManagerService.SYSTEM_PERMISSION_TARGET_ID, + PermissionType.CREATE_USER, "apollo"); + rolePermissionService.createPermission(createUserPermission); + } + // create user role init + Role createUserRole = createRole(SystemRoleManagerService.CREATE_USER_ROLE_NAME, "apollo"); + rolePermissionService.createRoleWithPermissions(createUserRole, + Sets.newHashSet(createUserPermission.getId())); + } + @Transactional public void createManageAppMasterRole(String appId, String operator) { Permission permission = createPermission(appId, PermissionType.MANAGE_APP_MASTER, operator); diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index d2fa0c34374..a247f60861e 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -212,7 +212,7 @@ "Component.Namespace.Master.Items.SortByKey": "Filter the configurations by key", "Component.Namespace.Master.Items.FilterItem": "Filter", "Component.Namespace.Master.Items.RevokeItemTips": "Revoke configuration changes", - "Component.Namespace.Master.Items.RevokeItem" :"Revoke", + "Component.Namespace.Master.Items.RevokeItem": "Revoke", "Component.Namespace.Master.Items.SyncItemTips": "Synchronize configurations among environments", "Component.Namespace.Master.Items.SyncItem": "Synchronize", "Component.Namespace.Master.Items.DiffItemTips": "Compare the configurations among environments", @@ -678,6 +678,10 @@ "Open.Manage.Consumer.AllowCreateApplicationTips": "(Allow third-party applications to create apps and grant them app administrator privileges.", "Open.Manage.Consumer.AllowCreateApplication.No": "no", "Open.Manage.Consumer.AllowCreateApplication.Yes": "yes", + "Open.Manage.Consumer.AllowCreateUser": "Allow user creation?", + "Open.Manage.Consumer.AllowCreateUserTips": "(Allow third-party applications to create users)", + "Open.Manage.Consumer.AllowCreateUser.No": "no", + "Open.Manage.Consumer.AllowCreateUser.Yes": "yes", "Open.Manage.Consumer.RateLimit.Enabled": "Whether to enable rate limit", "Open.Manage.Consumer.RateLimit.Enabled.Tips": "(After enabling this feature, when third-party applications publish configurations on Apollo, their traffic will be controlled according to the configured QPS limit)", "Open.Manage.Consumer.RateLimitValue": "Rate limiting QPS", @@ -795,25 +799,25 @@ "Config.Diff.PleaseChooseTwoCluster": "Please select at least two clusters", "Config.Diff.TextDiffMostChooseTwoCluster": "Please select at most two clusters", "ConfigExport.Title": "Config Export/Import", - "ConfigExport.Env.TitleTips" : "(The data (application, cluster and namespace) of one cluster can be migrated to another cluster by exporting and importing the configuration)", - "ConfigExport.App.TitleTips" : "(All configurations of an application in a specified environment and cluster can be migrated to another cluster by exporting and importing the configuration)", - "ConfigExport.SelectExportEnv" : "Select the environment to export", - "ConfigExport.SelectImportEnv" : "Select the environment to import", - "ConfigExport.ExportTips" : "In case of large amount of data, the export speed is slow. Please wait patiently", - "ConfigExport.ImportConflictLabel" : "How to deal with existing namespaces when importing", - "ConfigExport.IgnoreExistedNamespace" : "Ignore existing namespaces", - "ConfigExport.OverwriteExistedNamespace" : "Overwrite existing namespace", - "ConfigExport.UploadFile" : "Upload the exported file", - "ConfigExport.UploadFileTip" : "Please upload the exported compressed file", - "ConfigExport.ImportSuccess" : "Import success", - "ConfigExport.ImportingTip" : "Importing, please wait patiently. After importing, please check whether the namespace configuration is correct. If it is correct, publish the namespace to take effect", - "ConfigExport.ImportFailed" : "Import failed", - "ConfigExport.ExportFailed" : "Export failed", - "ConfigExport.NoPermissionTip" : "You are not this project's administrator. Only project administrators have the permission to export/import configurations.", - "ConfigExport.ExportSuccess" : "Exporting data. The data volume will cause slow speed. Please wait patiently", - "ConfigExport.ImportTips" : "After the import is completed, please check whether the namespace configuration is correct. After the check is correct, it needs to be published to take effect", - "ConfigExport.Export" : "Export", - "ConfigExport.Import" : "Import", + "ConfigExport.Env.TitleTips": "(The data (application, cluster and namespace) of one cluster can be migrated to another cluster by exporting and importing the configuration)", + "ConfigExport.App.TitleTips": "(All configurations of an application in a specified environment and cluster can be migrated to another cluster by exporting and importing the configuration)", + "ConfigExport.SelectExportEnv": "Select the environment to export", + "ConfigExport.SelectImportEnv": "Select the environment to import", + "ConfigExport.ExportTips": "In case of large amount of data, the export speed is slow. Please wait patiently", + "ConfigExport.ImportConflictLabel": "How to deal with existing namespaces when importing", + "ConfigExport.IgnoreExistedNamespace": "Ignore existing namespaces", + "ConfigExport.OverwriteExistedNamespace": "Overwrite existing namespace", + "ConfigExport.UploadFile": "Upload the exported file", + "ConfigExport.UploadFileTip": "Please upload the exported compressed file", + "ConfigExport.ImportSuccess": "Import success", + "ConfigExport.ImportingTip": "Importing, please wait patiently. After importing, please check whether the namespace configuration is correct. If it is correct, publish the namespace to take effect", + "ConfigExport.ImportFailed": "Import failed", + "ConfigExport.ExportFailed": "Export failed", + "ConfigExport.NoPermissionTip": "You are not this project's administrator. Only project administrators have the permission to export/import configurations.", + "ConfigExport.ExportSuccess": "Exporting data. The data volume will cause slow speed. Please wait patiently", + "ConfigExport.ImportTips": "After the import is completed, please check whether the namespace configuration is correct. After the check is correct, it needs to be published to take effect", + "ConfigExport.Export": "Export", + "ConfigExport.Import": "Import", "ConfigExport.Download": "Download", "ConfigExport.Env.Tab": "Operate By Environment", "ConfigExport.App.Tab": "Operate By Application Cluster", @@ -949,20 +953,20 @@ "Global.NameSpace": "NameSpace Name", "Global.Key": "Key", "Global.Value": "Value", - "Global.ValueSearch.Tips" : "(Fuzzy search, key can be the name or content of the configuration item, value is the value of the configuration item.)", - "Global.Operate" : "Operate", - "Global.Expand" : "Expand", - "Global.Abbreviate" : "Abbreviate", - "Global.JumpToEditPage" : "Jump to edit page", + "Global.ValueSearch.Tips": "(Fuzzy search, key can be the name or content of the configuration item, value is the value of the configuration item.)", + "Global.Operate": "Operate", + "Global.Expand": "Expand", + "Global.Abbreviate": "Abbreviate", + "Global.JumpToEditPage": "Jump to edit page", "Item.GlobalSearchByKey": "Search by Key", "Item.GlobalSearchByValue": "Search by Value", "Item.GlobalSearch": "Search", "Item.GlobalSearchSystemError": "System error, please try again or contact the system administrator", "Item.GlobalSearch.Tips": "Search hint", - "ApolloGlobalSearch.NoData" : "No data yet, please search or add", - "Paging.TotalItems.part1" : "Total of", - "Paging.TotalItems.part2" : "records", - "Paging.DisplayNumber" : "per/Page", - "Paging.PageNumberOne" : "First", - "Paging.PageNumberLast" : "Last" + "ApolloGlobalSearch.NoData": "No data yet, please search or add", + "Paging.TotalItems.part1": "Total of", + "Paging.TotalItems.part2": "records", + "Paging.DisplayNumber": "per/Page", + "Paging.PageNumberOne": "First", + "Paging.PageNumberLast": "Last" } diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json index d7dd57c0121..576c0dc4080 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -320,7 +320,7 @@ "Component.Publish.OpPublish": "发布", "Component.Rollback.To": "回滚到", "Component.Rollback.Tips": "此操作将会回滚到上一个发布版本,且当前版本作废,但不影响正在修改的配置。可在发布历史页面查看当前生效的版本", - "Component.RollbackTo.Tips":"此操作将会回滚到此发布版本,且当前版本作废,但不影响正在修改的配置", + "Component.RollbackTo.Tips": "此操作将会回滚到此发布版本,且当前版本作废,但不影响正在修改的配置", "Component.Rollback.ClickToView": "点击查看", "Component.Rollback.ItemType": "Type", "Component.Rollback.ItemKey": "Key", @@ -678,6 +678,10 @@ "Open.Manage.Consumer.AllowCreateApplicationTips": "(允许第三方应用创建app,并且对创建出的app,拥有应用管理员的权限)", "Open.Manage.Consumer.AllowCreateApplication.No": "否", "Open.Manage.Consumer.AllowCreateApplication.Yes": "是", + "Open.Manage.Consumer.AllowCreateUser": "允许创建用户?", + "Open.Manage.Consumer.AllowCreateUserTips": "(允许第三方应用创建用户)", + "Open.Manage.Consumer.AllowCreateUser.No": "否", + "Open.Manage.Consumer.AllowCreateUser.Yes": "是", "Open.Manage.Consumer.RateLimit.Enabled": "是否启用限流", "Open.Manage.Consumer.RateLimit.Enabled.Tips": "(开启后,第三方应用在 Apollo 上发布配置时,会根据配置的 QPS 限制,控制其流量)", "Open.Manage.Consumer.RateLimitValue": "限流QPS", @@ -795,25 +799,25 @@ "Config.Diff.PleaseChooseTwoCluster": "请至少选择两个集群", "Config.Diff.TextDiffMostChooseTwoCluster": "文本比对至多选择两个集群", "ConfigExport.Title": "配置导出导入", - "ConfigExport.Env.TitleTips" : "(通过导出导入配置,把一个集群的数据(应用、集群、Namespace)迁移到另外一个集群)", - "ConfigExport.App.TitleTips" : "(通过导出导入配置,把一个应用在指定环境和集群下的所有配置迁移到另外一个集群)", - "ConfigExport.SelectExportEnv" : "选择导出的环境", - "ConfigExport.SelectImportEnv" : "选择导入的环境", - "ConfigExport.ImportConflictLabel" : "导入时该如何处理已存在的 Namespace", - "ConfigExport.ExportSuccess" : "正在导出数据,数据量大会导致速度慢,请耐心等待", - "ConfigExport.ExportTips" : "数据量大的情况下,导出速度较慢请耐心等待", - "ConfigExport.IgnoreExistedNamespace" : "跳过已存在的 Namespace", - "ConfigExport.OverwriteExistedNamespace" : "覆盖已存在的 Namespace", - "ConfigExport.UploadFile" : "上传导出的文件", - "ConfigExport.UploadFileTip" : "请上传导出的压缩文件", - "ConfigExport.ImportSuccess" : "导入成功", - "ConfigExport.ImportingTip" : "正在导入,请耐心等待。导入完成后,请检查 Namespace 的配置是否正确,如果无误再发布 Namespace", - "ConfigExport.ImportFailed" : "导入失败", - "ConfigExport.ExportFailed" : "导出失败", - "ConfigExport.NoPermissionTip" : "您不是应用管理员, 只有应用管理员才有导出/导入配置的权限", - "ConfigExport.Export" : "导出", - "ConfigExport.Import" : "导入", - "ConfigExport.ImportTips" : "导入完成之后,请检查 Namespace 的配置是否正确,检查无误后需要发布才能生效", + "ConfigExport.Env.TitleTips": "(通过导出导入配置,把一个集群的数据(应用、集群、Namespace)迁移到另外一个集群)", + "ConfigExport.App.TitleTips": "(通过导出导入配置,把一个应用在指定环境和集群下的所有配置迁移到另外一个集群)", + "ConfigExport.SelectExportEnv": "选择导出的环境", + "ConfigExport.SelectImportEnv": "选择导入的环境", + "ConfigExport.ImportConflictLabel": "导入时该如何处理已存在的 Namespace", + "ConfigExport.ExportSuccess": "正在导出数据,数据量大会导致速度慢,请耐心等待", + "ConfigExport.ExportTips": "数据量大的情况下,导出速度较慢请耐心等待", + "ConfigExport.IgnoreExistedNamespace": "跳过已存在的 Namespace", + "ConfigExport.OverwriteExistedNamespace": "覆盖已存在的 Namespace", + "ConfigExport.UploadFile": "上传导出的文件", + "ConfigExport.UploadFileTip": "请上传导出的压缩文件", + "ConfigExport.ImportSuccess": "导入成功", + "ConfigExport.ImportingTip": "正在导入,请耐心等待。导入完成后,请检查 Namespace 的配置是否正确,如果无误再发布 Namespace", + "ConfigExport.ImportFailed": "导入失败", + "ConfigExport.ExportFailed": "导出失败", + "ConfigExport.NoPermissionTip": "您不是应用管理员, 只有应用管理员才有导出/导入配置的权限", + "ConfigExport.Export": "导出", + "ConfigExport.Import": "导入", + "ConfigExport.ImportTips": "导入完成之后,请检查 Namespace 的配置是否正确,检查无误后需要发布才能生效", "ConfigExport.Download": "下载", "ConfigExport.Env.Tab": "按环境操作", "ConfigExport.App.Tab": "按应用集群操作", @@ -919,7 +923,7 @@ "ApolloAuditLog.DataInfluence.FieldName": "实体属性名", "ApolloAuditLog.DataInfluence.FieldNewValue": "属性值记录", "ApolloAuditLog.DataInfluence.Fields": "变化字段", - "ApolloAuditLog.DataInfluence.MatchedFields": "匹配字段", + "ApolloAuditLog.DataInfluence.MatchedFields": "匹配字段", "ApolloAuditLog.DataInfluence.HappenedTime": "记录时间", "ApolloAuditLog.TraceIdTips": "链路唯一ID", "ApolloAuditLog.OpName": "操作名称", @@ -949,20 +953,20 @@ "Global.NameSpace": "命名空间", "Global.Key": "Key", "Global.Value": "Value", - "Global.ValueSearch.Tips" : "(模糊搜索,key可为配置项名称或content,value为配置项值)", - "Global.Operate" : "操作", - "Global.Expand" : "展开", - "Global.Abbreviate" : "缩略", - "Global.JumpToEditPage" : "跳转到编辑页面", + "Global.ValueSearch.Tips": "(模糊搜索,key可为配置项名称或content,value为配置项值)", + "Global.Operate": "操作", + "Global.Expand": "展开", + "Global.Abbreviate": "缩略", + "Global.JumpToEditPage": "跳转到编辑页面", "Item.GlobalSearchByKey": "按照Key值检索", "Item.GlobalSearchByValue": "按照Value值检索", "Item.GlobalSearch": "查询", "Item.GlobalSearchSystemError": "系统出错,请重试或联系系统负责人", "Item.GlobalSearch.Tips": "搜索提示", - "ApolloGlobalSearch.NoData" : "暂无数据,请进行检索或者添加", - "Paging.TotalItems.part1" : "共", - "Paging.TotalItems.part2" : "条记录", - "Paging.DisplayNumber" : "条/页", - "Paging.PageNumberOne" : "首页", - "Paging.PageNumberLast" : "尾页" + "ApolloGlobalSearch.NoData": "暂无数据,请进行检索或者添加", + "Paging.TotalItems.part1": "共", + "Paging.TotalItems.part2": "条记录", + "Paging.DisplayNumber": "条/页", + "Paging.PageNumberOne": "首页", + "Paging.PageNumberLast": "尾页" } diff --git a/apollo-portal/src/main/resources/static/open/add-consumer.html b/apollo-portal/src/main/resources/static/open/add-consumer.html index 94c490c2b35..1d5c83863b9 100644 --- a/apollo-portal/src/main/resources/static/open/add-consumer.html +++ b/apollo-portal/src/main/resources/static/open/add-consumer.html @@ -18,265 +18,283 @@ - - - - - - - - - {{'Open.Manage.Title' | translate }} + + + + + + + + + {{'Open.Manage.Title' | translate }} - + -
-
+
+
-
- -
-
{{'Open.Manage.CreateThirdApp' | translate }} - - {{'Open.Manage.CreateThirdAppTips' | translate }} - -
-
-
-
- -
- - {{'Open.Manage.ThirdAppIdTips' | translate }} -
-
- -
-
-

-
-
+
+ +
+
{{'Open.Manage.CreateThirdApp' | translate }} + + {{'Open.Manage.CreateThirdAppTips' | translate }} + +
+
+ +
+ +
+ + {{'Open.Manage.ThirdAppIdTips' | translate }} +
+
+ +
+
+

+
+
-
- -
- - {{'Open.Manage.Consumer.AllowCreateApplicationTips' | translate }} -
-
+
+ +
+ + {{'Open.Manage.Consumer.AllowCreateApplicationTips' | translate }} +
+
-
- -
- - {{ 'Open.Manage.Consumer.RateLimit.Enabled.Tips' | translate }} -
-
+
+ +
+ + {{'Open.Manage.Consumer.AllowCreateUserTips' | translate }} +
+
-
- -
- - {{'Open.Manage.Consumer.RateLimitValueTips' | translate }} -
-
+
+ +
+ + {{ 'Open.Manage.Consumer.RateLimit.Enabled.Tips' | translate }} +
+
-
- -
- -
-
-
- -
- - {{'Open.Manage.ThirdAppNameTips' | translate }} -
-
-
- -
- -
-
+
+ +
+ + {{'Open.Manage.Consumer.RateLimitValueTips' | translate }} +
+
-
-
- -
-
- -
+
+ +
+ +
+
+
+ +
+ + {{'Open.Manage.ThirdAppNameTips' | translate }} +
+
+
+ +
+ +
+
-
-
{{'Open.Manage.GrantPermission' | translate }} - - {{'Open.Manage.GrantPermissionTips' | translate }} - -
-
-
+
+
+ +
+
+
+
-
- -
- -
-
-
- -
- -
-
-
- -
- - {{'Open.Manage.ManagedNamespaceTips' | translate }} -
-
-
- -
- - -
-
-
- -
-
- -
- {{'Open.Manage.GrantEnvTips' | translate }} -
-
-
-
- -
-
- +
+
{{'Open.Manage.GrantPermission' | translate }} + + {{'Open.Manage.GrantPermissionTips' | translate }} + +
+
+
-
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + {{'Open.Manage.ManagedNamespaceTips' | translate }} +
+
+
+ +
+ + +
+
+
+ +
+
+ +
+ {{'Open.Manage.GrantEnvTips' | translate }} +
+
+
+
+ +
+
+ -
+
-
-

{{'Common.IsRootUser' | translate }}

-
+
-
-
+
+

{{'Common.IsRootUser' | translate }}

+
-
+
+
- - +
- - - - - - - + + - - - - - - + + + + + + + - - + + + + + + - + + - - - - - - - - - - - + - + + + + + + + + + + + - - - + - + + + + + diff --git a/apollo-portal/src/main/resources/static/open/manage.html b/apollo-portal/src/main/resources/static/open/manage.html index dfe0db042fd..7b74e2e84be 100644 --- a/apollo-portal/src/main/resources/static/open/manage.html +++ b/apollo-portal/src/main/resources/static/open/manage.html @@ -18,161 +18,181 @@ - - - - - - - - - - {{'Open.Manage.Title' | translate }} + + + + + + + + + + {{'Open.Manage.Title' | translate }} - - -
-
- -
- -
-
-
-
{{'Open.Manage.CreateThirdApp' | translate }} - - {{'Open.Manage.CreateThirdAppTips' | translate }} - -
-
- -
-
- - - - - - - - - - - - - - - - - - - - - -
{{'Common.AppId' | translate }}{{'Common.AppName' | translate }}{{'Open.Manage.Consumer.AllowCreateApplication' | translate }}{{'Open.Manage.Consumer.RateLimitValue' | translate }}{{'Common.Department' | translate }}{{'Common.AppOwner' | translate }}/{{'Common.Email' | translate }}{{'Common.Operation' | translate}}
{{ consumer.appId }}{{ consumer.name }} -
- {{'Open.Manage.Consumer.AllowCreateApplication.Yes' | translate}} -
-
- {{'Open.Manage.Consumer.AllowCreateApplication.No' | translate}} -
-
- {{ consumer.rateLimit && consumer.rateLimit > 0 ? consumer.rateLimit : 'Open.Manage.Consumer.RateLimitValue.Display' | translate }} - {{ consumer.orgName + '(' + consumer.orgId + ')' }}{{ consumer.ownerName }}/{{ consumer.ownerEmail }} - - - - -
-
-
-
-
-
-
{{'Index.LoadMore' | translate }}
-
-
- - - - - -
- -
- -
-

{{'Common.IsRootUser' | translate }}

-
- + + +
+
+ +
+ +
+
+
+
{{'Open.Manage.CreateThirdApp' | translate }} + + {{'Open.Manage.CreateThirdAppTips' | translate }} + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{{'Common.AppId' | translate }}{{'Common.AppName' | translate }}{{'Open.Manage.Consumer.AllowCreateApplication' | translate }} + {{'Open.Manage.Consumer.AllowCreateUser' | translate }}{{'Open.Manage.Consumer.RateLimitValue' | translate }}{{'Common.Department' | translate }}{{'Common.AppOwner' | translate }}/{{'Common.Email' | translate + }} + {{'Common.Operation' | translate}}
{{ consumer.appId }}{{ consumer.name }} +
+ {{'Open.Manage.Consumer.AllowCreateApplication.Yes' | translate}} +
+
+ {{'Open.Manage.Consumer.AllowCreateApplication.No' | translate}} +
+
+
+ {{'Open.Manage.Consumer.AllowCreateUser.Yes' | translate}} +
+
+ {{'Open.Manage.Consumer.AllowCreateUser.No' | translate}} +
+
+ {{ consumer.rateLimit && consumer.rateLimit > 0 ? consumer.rateLimit : + 'Open.Manage.Consumer.RateLimitValue.Display' | translate }} + {{ consumer.orgName + '(' + consumer.orgId + ')' }}{{ consumer.ownerName }}/{{ consumer.ownerEmail }} + + + + +
+
+
+
+
+
+
{{'Index.LoadMore' | translate }}
+
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + +
+

{{'Common.IsRootUser' | translate }}

+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java index 04fcc02c156..46573d7bcd1 100644 --- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java @@ -16,6 +16,9 @@ */ package com.ctrip.framework.apollo.openapi.service; +import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.common.exception.NotFoundException; +import com.ctrip.framework.apollo.portal.service.SystemRoleManagerService; import com.ctrip.framework.apollo.openapi.entity.Consumer; import com.ctrip.framework.apollo.openapi.entity.ConsumerRole; import com.ctrip.framework.apollo.openapi.entity.ConsumerToken; @@ -51,6 +54,7 @@ @SpringBootTest @ContextConfiguration(classes = ConsumerService.class) public class ConsumerServiceTest { + @SpyBean private ConsumerService consumerService; @MockBean @@ -304,6 +308,160 @@ void allowCreateApplication() { assertEquals(consumerId, consumerInfo.getConsumerId()); } + + @Test + void notAllowCreateUser() { + final String appId = "appId-consumer-user-001"; + final String token = "token-user-001"; + final long consumerId = 1001L; + + Consumer consumer = new Consumer(); + consumer.setAppId(appId); + consumer.setId(consumerId); + when(consumerRepository.findByAppId(eq(appId))).thenReturn(consumer); + + ConsumerToken consumerToken = new ConsumerToken(); + consumerToken.setToken(token); + consumerToken.setRateLimit(0); + when(consumerTokenRepository.findByConsumerId(eq(consumerId))).thenReturn(consumerToken); + + // getCreateUserRole 返回 null,即 CREATE_USER 角色不存在 + when(rolePermissionService + .findRoleByRoleName(eq(SystemRoleManagerService.CREATE_USER_ROLE_NAME))).thenReturn(null); + + ConsumerInfo consumerInfo = consumerService.getConsumerInfoByAppId(appId); + assertFalse(consumerInfo.isAllowCreateUser()); + assertEquals(appId, consumerInfo.getAppId()); + assertEquals(token, consumerInfo.getToken()); + } + + @Test + void allowCreateUser() { + final String appId = "appId-consumer-user-002"; + final String token = "token-user-002"; + final long consumerId = 1002L; + final long createUserRoleId = 9001L; + + Consumer consumer = new Consumer(); + consumer.setAppId(appId); + consumer.setId(consumerId); + when(consumerRepository.findByAppId(eq(appId))).thenReturn(consumer); + + ConsumerToken consumerToken = new ConsumerToken(); + consumerToken.setToken(token); + consumerToken.setRateLimit(0); + when(consumerTokenRepository.findByConsumerId(eq(consumerId))).thenReturn(consumerToken); + + // CREATE_USER 角色存在,且 consumer 已被分配该角色 + Role createUserRole = + createRole(createUserRoleId, SystemRoleManagerService.CREATE_USER_ROLE_NAME); + when(rolePermissionService + .findRoleByRoleName(eq(SystemRoleManagerService.CREATE_USER_ROLE_NAME))) + .thenReturn(createUserRole); + + ConsumerRole consumerRole = createConsumerRole(consumerId, createUserRoleId); + when(consumerRoleRepository.findByConsumerIdAndRoleId(eq(consumerId), eq(createUserRoleId))) + .thenReturn(consumerRole); + + ConsumerInfo consumerInfo = consumerService.getConsumerInfoByAppId(appId); + assertTrue(consumerInfo.isAllowCreateUser()); + assertEquals(appId, consumerInfo.getAppId()); + assertEquals(token, consumerInfo.getToken()); + assertEquals(consumerId, consumerInfo.getConsumerId()); + } + + @Test + void assignCreateUserRoleToConsumer_success() { + final String token = "token-assign-user-001"; + final long consumerId = 2001L; + final long createUserRoleId = 9002L; + final String operator = "admin"; + + // token 对应的 consumerId + doReturn(consumerId).when(consumerService).getConsumerIdByToken(token); + + // CREATE_USER 角色存在 + Role createUserRole = + createRole(createUserRoleId, SystemRoleManagerService.CREATE_USER_ROLE_NAME); + when(rolePermissionService + .findRoleByRoleName(eq(SystemRoleManagerService.CREATE_USER_ROLE_NAME))) + .thenReturn(createUserRole); + + // consumer 尚未被分配该角色 + when(consumerRoleRepository.findByConsumerIdAndRoleId(eq(consumerId), eq(createUserRoleId))) + .thenReturn(null); + + UserInfo userInfo = createUser(operator); + when(userInfoHolder.getUser()).thenReturn(userInfo); + + ConsumerRole newConsumerRole = createConsumerRole(consumerId, createUserRoleId); + doReturn(newConsumerRole).when(consumerService).createConsumerRole(consumerId, createUserRoleId, + operator); + when(consumerRoleRepository.save(eq(newConsumerRole))).thenReturn(newConsumerRole); + + ConsumerRole result = consumerService.assignCreateUserRoleToConsumer(token); + + assertNotNull(result); + assertEquals(consumerId, result.getConsumerId()); + assertEquals(createUserRoleId, result.getRoleId()); + verify(consumerRoleRepository, times(1)).save(eq(newConsumerRole)); + } + + @Test + void assignCreateUserRoleToConsumer_alreadyAssigned() { + final String token = "token-assign-user-002"; + final long consumerId = 2002L; + final long createUserRoleId = 9003L; + + doReturn(consumerId).when(consumerService).getConsumerIdByToken(token); + + Role createUserRole = + createRole(createUserRoleId, SystemRoleManagerService.CREATE_USER_ROLE_NAME); + when(rolePermissionService + .findRoleByRoleName(eq(SystemRoleManagerService.CREATE_USER_ROLE_NAME))) + .thenReturn(createUserRole); + + // consumer 已经被分配了该角色 + ConsumerRole existingConsumerRole = createConsumerRole(consumerId, createUserRoleId); + when(consumerRoleRepository.findByConsumerIdAndRoleId(eq(consumerId), eq(createUserRoleId))) + .thenReturn(existingConsumerRole); + + ConsumerRole result = consumerService.assignCreateUserRoleToConsumer(token); + + // 直接返回已有角色,不应再次 save + assertNotNull(result); + assertEquals(consumerId, result.getConsumerId()); + verify(consumerRoleRepository, never()).save(any()); + } + + @Test + void assignCreateUserRoleToConsumer_illegalToken() { + final String illegalToken = "illegal-token"; + + // token 找不到对应的 consumerId + doReturn(null).when(consumerService).getConsumerIdByToken(illegalToken); + + assertThrows(BadRequestException.class, + () -> consumerService.assignCreateUserRoleToConsumer(illegalToken)); + verify(consumerRoleRepository, never()).save(any()); + } + + @Test + void assignCreateUserRoleToConsumer_roleNotFound() { + final String token = "token-assign-user-003"; + final long consumerId = 2003L; + + doReturn(consumerId).when(consumerService).getConsumerIdByToken(token); + + // CREATE_USER 角色不存在(未初始化) + when(rolePermissionService + .findRoleByRoleName(eq(SystemRoleManagerService.CREATE_USER_ROLE_NAME))).thenReturn(null); + + assertThrows(NotFoundException.class, + () -> consumerService.assignCreateUserRoleToConsumer(token)); + verify(consumerRoleRepository, never()).save(any()); + } + private Consumer createConsumer(String name, String appId, String ownerName) { Consumer consumer = new Consumer(); diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/UserControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/UserControllerTest.java new file mode 100644 index 00000000000..32fba4ea1b7 --- /dev/null +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/UserControllerTest.java @@ -0,0 +1,350 @@ +/* + * Copyright 2025 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.openapi.v1.controller; + +import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.openapi.model.OpenUserDTO; +import com.ctrip.framework.apollo.openapi.model.UserInfo; +import com.ctrip.framework.apollo.portal.entity.po.UserPO; +import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService; +import com.ctrip.framework.apollo.portal.util.checker.AuthUserPasswordChecker; +import com.ctrip.framework.apollo.portal.util.checker.CheckResult; +import com.google.gson.Gson; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Unit tests for OpenAPI UserController + * + * @author dreamweaver + */ +@RunWith(MockitoJUnitRunner.class) +public class UserControllerTest { + + @Mock + private SpringSecurityUserService userService; + + @Mock + private AuthUserPasswordChecker passwordChecker; + + private UserController userController; + + private final Gson gson = new Gson(); + + @Before + public void setUp() { + userController = new UserController(userService, passwordChecker); + } + + // Helper to build a portal UserInfo BO (what UserService returns) + private com.ctrip.framework.apollo.portal.entity.bo.UserInfo portalUser(String userId, + String name, String email) { + com.ctrip.framework.apollo.portal.entity.bo.UserInfo u = + new com.ctrip.framework.apollo.portal.entity.bo.UserInfo(); + u.setUserId(userId); + u.setName(name); + u.setEmail(email); + u.setEnabled(1); + return u; + } + + @Test + public void testCreateUser_Success() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("testuser"); + openUserDTO.setPassword("StrongP@ssw0rd"); + openUserDTO.setEmail("testuser@example.com"); + openUserDTO.setUserDisplayName("Test User"); + openUserDTO.setEnabled(OpenUserDTO.EnabledEnum.NUMBER_1); + + CheckResult checkResult = new CheckResult(true, ""); + when(passwordChecker.checkWeakPassword(anyString())).thenReturn(checkResult); + doNothing().when(userService).create(any(UserPO.class)); + + when(userService.findByUserId("testuser")) + .thenReturn(portalUser("testuser", "Test User", "testuser@example.com")); + + // Act + ResponseEntity response = userController.createUser(openUserDTO); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertNotNull(response.getBody()); + assertEquals("testuser", response.getBody().getUserId()); + + // Verify + ArgumentCaptor userPOCaptor = ArgumentCaptor.forClass(UserPO.class); + verify(userService, times(1)).create(userPOCaptor.capture()); + + UserPO capturedUser = userPOCaptor.getValue(); + assertEquals("testuser", capturedUser.getUsername()); + assertEquals("testuser@example.com", capturedUser.getEmail()); + assertEquals("Test User", capturedUser.getUserDisplayName()); + assertEquals(1, capturedUser.getEnabled()); + assertNotNull(capturedUser.getPassword()); + } + + @Test + public void testCreateUser_WithDefaultValues() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("testuser2"); + openUserDTO.setPassword("StrongP@ssw0rd"); + openUserDTO.setEmail("testuser2@example.com"); + // Not setting userDisplayName and enabled + + CheckResult checkResult = new CheckResult(true, ""); + when(passwordChecker.checkWeakPassword(anyString())).thenReturn(checkResult); + doNothing().when(userService).create(any(UserPO.class)); + + when(userService.findByUserId("testuser2")) + .thenReturn(portalUser("testuser2", "testuser2", "testuser2@example.com")); + + // Act + ResponseEntity response = userController.createUser(openUserDTO); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + + // Verify default values are set + ArgumentCaptor userPOCaptor = ArgumentCaptor.forClass(UserPO.class); + verify(userService, times(1)).create(userPOCaptor.capture()); + + UserPO capturedUser = userPOCaptor.getValue(); + assertEquals("testuser2", capturedUser.getUserDisplayName()); // Should default to username + assertEquals(1, capturedUser.getEnabled()); // Should default to 1 + } + + @Test + public void testCreateUser_EmptyUsername() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername(""); + openUserDTO.setPassword("StrongP@ssw0rd"); + openUserDTO.setEmail("test@example.com"); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.createUser(openUserDTO)); + + // Verify that create was never called + verify(userService, times(0)).create(any(UserPO.class)); + } + + @Test + public void testCreateUser_EmptyPassword() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("testuser"); + openUserDTO.setPassword(""); + openUserDTO.setEmail("test@example.com"); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.createUser(openUserDTO)); + + verify(userService, times(0)).create(any(UserPO.class)); + } + + @Test + public void testCreateUser_EmptyEmail() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("testuser"); + openUserDTO.setPassword("StrongP@ssw0rd"); + openUserDTO.setEmail(""); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.createUser(openUserDTO)); + + verify(userService, times(0)).create(any(UserPO.class)); + } + + @Test + public void testCreateUser_WeakPassword() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("testuser"); + openUserDTO.setPassword("weak"); + openUserDTO.setEmail("test@example.com"); + + CheckResult checkResult = new CheckResult(false, "Password is too weak"); + when(passwordChecker.checkWeakPassword(anyString())).thenReturn(checkResult); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.createUser(openUserDTO)); + + verify(userService, times(0)).create(any(UserPO.class)); + } + + @Test + public void testCreateUser_UserAlreadyExists() { + // Arrange + OpenUserDTO openUserDTO = new OpenUserDTO(); + openUserDTO.setUsername("existinguser"); + openUserDTO.setPassword("StrongP@ssw0rd"); + openUserDTO.setEmail("test@example.com"); + + CheckResult checkResult = new CheckResult(true, ""); + when(passwordChecker.checkWeakPassword(anyString())).thenReturn(checkResult); + doThrow(BadRequestException.userAlreadyExists("existinguser")).when(userService) + .create(any(UserPO.class)); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.createUser(openUserDTO)); + } + + @Test + public void testGetUserByUserId_Success() { + // Arrange + String userId = "testuser"; + when(userService.findByUserId(userId)) + .thenReturn(portalUser(userId, "Test User", "testuser@example.com")); + + // Act + ResponseEntity response = userController.getUserByUserId(userId); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertNotNull(response.getBody()); + assertEquals(userId, response.getBody().getUserId()); + assertEquals("Test User", response.getBody().getName()); + assertEquals("testuser@example.com", response.getBody().getEmail()); + + verify(userService, times(1)).findByUserId(userId); + } + + @Test + public void testGetUserByUserId_UserNotFound() { + // Arrange + String userId = "nonexistent"; + when(userService.findByUserId(userId)).thenReturn(null); + + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.getUserByUserId(userId)); + + verify(userService, times(1)).findByUserId(userId); + } + + @Test + public void testSearchUsers_Success() { + // Arrange + List users = Arrays.asList( + portalUser("user1", "User One", "user1@example.com"), + portalUser("user2", "User Two", "user2@example.com") + ); + when(userService.searchUsers(anyString(), anyInt(), anyInt(), anyBoolean())).thenReturn(users); + + // Act + ResponseEntity> response = userController.searchUsers("user", false, 0, 10); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + assertNotNull(response.getBody()); + assertEquals(2, response.getBody().size()); + assertEquals("user1", response.getBody().get(0).getUserId()); + assertEquals("user2", response.getBody().get(1).getUserId()); + + verify(userService, times(1)).searchUsers("user", 0, 10, false); + } + + @Test + public void testSearchUsers_WithIncludeInactiveUsers() { + // Arrange + when(userService.searchUsers(anyString(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(Collections.emptyList()); + + // Act + ResponseEntity> response = userController.searchUsers("test", true, 0, 20); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + + verify(userService, times(1)).searchUsers("test", 0, 20, true); + } + + @Test + public void testSearchUsers_DefaultParameters() { + // Arrange + when(userService.searchUsers(anyString(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(Collections.emptyList()); + + // Act + ResponseEntity> response = userController.searchUsers("", false, 0, 10); + + // Assert + assertEquals(200, response.getStatusCodeValue()); + + // Verify default values are used + verify(userService, times(1)).searchUsers("", 0, 10, false); + } + + @Test + public void testSearchUsers_InvalidLimit_TooHigh() { + // Act & Assert + assertThrows(BadRequestException.class, + () -> userController.searchUsers("test", false, 0, 101)); + + verify(userService, times(0)).searchUsers(anyString(), anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testSearchUsers_InvalidLimit_Zero() { + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.searchUsers("test", false, 0, 0)); + + verify(userService, times(0)).searchUsers(anyString(), anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testSearchUsers_InvalidLimit_Negative() { + // Act & Assert + assertThrows(BadRequestException.class, () -> userController.searchUsers("test", false, 0, -1)); + + verify(userService, times(0)).searchUsers(anyString(), anyInt(), anyInt(), anyBoolean()); + } + + @Test + public void testSearchUsers_InvalidOffset() { + // Act & Assert + assertThrows(BadRequestException.class, + () -> userController.searchUsers("test", false, -1, 10)); + + verify(userService, times(0)).searchUsers(anyString(), anyInt(), anyInt(), anyBoolean()); + } +} diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/AbstractPermissionValidatorTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/AbstractPermissionValidatorTest.java index 790a9dd04ed..152fa684cc8 100644 --- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/AbstractPermissionValidatorTest.java +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/AbstractPermissionValidatorTest.java @@ -205,6 +205,7 @@ public void testHasManageAppMasterPermission_WhenWithPermission() { } private static class AbstractPermissionValidatorImpl extends AbstractPermissionValidator { + @Override public boolean hasCreateAppNamespacePermission(String appId, AppNamespace appNamespace) { return false; @@ -234,10 +235,21 @@ protected boolean hasPermissions(List requiredPerms) { public boolean hasCreateApplicationPermission(String userId) { return false; } + + @Override + public boolean hasCreateUserPermission() { + return false; + } + + @Override + public boolean hasCreateUserPermission(String userId) { + return false; + } } private static class AbstractPermissionValidatorWithPermissionsImpl extends AbstractPermissionValidator { + private final List allowed; AbstractPermissionValidatorWithPermissionsImpl(List allowed) { @@ -273,5 +285,15 @@ protected boolean hasPermissions(List requiredPerms) { public boolean hasCreateApplicationPermission(String userId) { return true; } + + @Override + public boolean hasCreateUserPermission() { + return true; + } + + @Override + public boolean hasCreateUserPermission(String userId) { + return true; + } } } diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java index 5e5ffa37539..1c86a538e6f 100644 --- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java @@ -97,4 +97,91 @@ void createAndAssignCreateApplicationRoleToConsumer() { .assignCreateApplicationRoleToConsumer(Mockito.eq(token)); Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any()); } + + @Test + void createAndAssignCreateUserRoleToConsumer() { + ConsumerService consumerService = Mockito.mock(ConsumerService.class); + ConsumerController consumerController = new ConsumerController(consumerService); + ConsumerCreateRequestVO requestVO = new ConsumerCreateRequestVO(); + requestVO.setAppId("appId1"); + requestVO.setName("app 1"); + requestVO.setOwnerName("user1"); + requestVO.setOrgId("orgId1"); + requestVO.setAllowCreateUser(true); + + final String token = "token-xxx"; + ConsumerToken consumerToken = new ConsumerToken(); + consumerToken.setToken(token); + Mockito.when( + consumerService.generateAndSaveConsumerToken(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(consumerToken); + + consumerController.create(requestVO, null); + + Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(1)).generateAndSaveConsumerToken(Mockito.any(), + Mockito.any(), Mockito.any()); + // allowCreateUser=true 时应调用 assignCreateUserRoleToConsumer + Mockito.verify(consumerService, Mockito.times(1)) + .assignCreateUserRoleToConsumer(Mockito.eq(token)); + // allowCreateApplication=false 时不应调用 assignCreateApplicationRoleToConsumer + Mockito.verify(consumerService, Mockito.times(0)) + .assignCreateApplicationRoleToConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any()); + } + + @Test + void createAndAssignBothCreateApplicationAndCreateUserRoleToConsumer() { + ConsumerService consumerService = Mockito.mock(ConsumerService.class); + ConsumerController consumerController = new ConsumerController(consumerService); + ConsumerCreateRequestVO requestVO = new ConsumerCreateRequestVO(); + requestVO.setAppId("appId1"); + requestVO.setName("app 1"); + requestVO.setOwnerName("user1"); + requestVO.setOrgId("orgId1"); + requestVO.setAllowCreateApplication(true); + requestVO.setAllowCreateUser(true); + + final String token = "token-xxx"; + ConsumerToken consumerToken = new ConsumerToken(); + consumerToken.setToken(token); + Mockito.when( + consumerService.generateAndSaveConsumerToken(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(consumerToken); + + consumerController.create(requestVO, null); + + Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(1)).generateAndSaveConsumerToken(Mockito.any(), + Mockito.any(), Mockito.any()); + // 两个权限都应被分配 + Mockito.verify(consumerService, Mockito.times(1)) + .assignCreateApplicationRoleToConsumer(Mockito.eq(token)); + Mockito.verify(consumerService, Mockito.times(1)) + .assignCreateUserRoleToConsumer(Mockito.eq(token)); + Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any()); + } + + @Test + void createWithoutAnySystemRole() { + ConsumerService consumerService = Mockito.mock(ConsumerService.class); + ConsumerController consumerController = new ConsumerController(consumerService); + ConsumerCreateRequestVO requestVO = new ConsumerCreateRequestVO(); + requestVO.setAppId("appId1"); + requestVO.setName("app 1"); + requestVO.setOwnerName("user1"); + requestVO.setOrgId("orgId1"); + // allowCreateApplication 和 allowCreateUser 均为默认 false + + consumerController.create(requestVO, null); + + Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(1)).generateAndSaveConsumerToken(Mockito.any(), + Mockito.any(), Mockito.any()); + // 两个权限均不应被分配 + Mockito.verify(consumerService, Mockito.times(0)) + .assignCreateApplicationRoleToConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(0)).assignCreateUserRoleToConsumer(Mockito.any()); + Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any()); + } }