From 052112a709734eaad3857d4a04831db9ae291b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Mon, 26 Aug 2024 17:33:31 +0800 Subject: [PATCH] [ISSUE #12017] Add the console backend API for auth section * Add user handling module * Add role handling module * Add permission handling module --- .../v3/auth/ConsolePermissionController.java | 110 ++++++++ .../v3/auth/ConsoleRoleController.java | 118 +++++++++ .../v3/auth/ConsoleUserController.java | 176 +++++++++++++ .../handler/auth/PermissionHandler.java | 61 +++++ .../console/handler/auth/RoleHandler.java | 70 +++++ .../console/handler/auth/UserHandler.java | 106 ++++++++ .../inner/auth/PermissionInnerHandler.java | 90 +++++++ .../handler/inner/auth/RoleInnerHandler.java | 72 ++++++ .../handler/inner/auth/UserInnerHandler.java | 240 ++++++++++++++++++ .../console/proxy/auth/PermissionProxy.java | 104 ++++++++ .../nacos/console/proxy/auth/RoleProxy.java | 120 +++++++++ .../nacos/console/proxy/auth/UserProxy.java | 175 +++++++++++++ 12 files changed, 1442 insertions(+) create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/auth/UserProxy.java diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java new file mode 100644 index 00000000000..461fd46ba9d --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.controller.v3.auth; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.auth.PermissionProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for handling HTTP requests related to permission operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/auth/permission") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsolePermissionController { + + private final PermissionProxy permissionProxy; + + /** + * Constructs a new ConsolePermissionController with the provided PermissionProxy. + * + * @param permissionProxy the proxy used for handling permission-related operations + */ + @Autowired + public ConsolePermissionController(PermissionProxy permissionProxy) { + this.permissionProxy = permissionProxy; + } + + /** + * Add a permission to a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Object createPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + permissionProxy.createPermission(role, resource, action); + return Result.success("add permission ok!"); + } + + + /** + * Delete a permission from a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Object deletePermission(@RequestParam String role, @RequestParam String resource, + @RequestParam String action) { + permissionProxy.deletePermission(role, resource, action); + return Result.success("delete permission ok!"); + } + + /** + * Query permissions of a role. + * + * @param role the role + * @param pageNo page index + * @param pageSize page size + * @param search the type of search (accurate or blur) + * @return permission of a role + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + public Result> getPermissionList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role, + @RequestParam(name = "search", defaultValue = "accurate") String search) { + Page permissionPage = permissionProxy.getPermissionList(role, pageNo, pageSize, search); + return Result.success(permissionPage); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java new file mode 100644 index 00000000000..9df1fe22d25 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.controller.v3.auth; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.auth.RoleProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +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; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/auth/role") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleRoleController { + + private final RoleProxy roleProxy; + + public ConsoleRoleController(RoleProxy roleProxy) { + this.roleProxy = roleProxy; + } + + /** + * Add a role to a user + * + *

This method is used for 2 functions: 1. create a role and bind it to GLOBAL_ADMIN. 2. bind a role to an user. + * + * @param role role name + * @param username username + * @return Code 200 and message 'add role ok!' + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Object createRole(@RequestParam String role, @RequestParam String username) { + roleProxy.createRole(role, username); + return Result.success("add role ok!"); + } + + /** + * Delete a role. If no username is specified, all users under this role are deleted. + * + * @param role role + * @param username username + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Object deleteRole(@RequestParam String role, + @RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) { + roleProxy.deleteRole(role, username); + return Result.success("delete role of user " + username + " ok!"); + } + + /** + * Get roles list with the option for accurate or fuzzy search. + * + * @param pageNo number index of page + * @param pageSize page size + * @param username optional, username of user + * @param role optional role + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return role list + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + public Result> getRoleList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "username", defaultValue = "") String username, + @RequestParam(name = "role", defaultValue = "") String role, + @RequestParam(name = "search", required = false, defaultValue = "accurate") String search) { + Page rolePage = roleProxy.getRoleList(pageNo, pageSize, username, role, search); + return Result.success(rolePage); + } + + /** + * Fuzzy matching role name . + * + * @param role role id + * @return role list + */ + @GetMapping("/search") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + public Result> getRoleListByRoleName(@RequestParam String role) { + List roles = roleProxy.getRoleListByRoleName(role); + return Result.success(roles); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java new file mode 100644 index 00000000000..ce14818ef0e --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java @@ -0,0 +1,176 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.controller.v3.auth; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.auth.UserProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import com.alibaba.nacos.plugin.auth.impl.utils.PasswordGeneratorUtil; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/auth/user") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleUserController { + + private final UserProxy userProxy; + + public ConsoleUserController(UserProxy userProxy) { + this.userProxy = userProxy; + } + + + /** + * Create a new user. + * + * @param username username + * @param password password + * @return ok if create succeed + * @throws IllegalArgumentException if user already exist + * @since 1.2.0 + */ + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + @PostMapping + public Object createUser(@RequestParam String username, @RequestParam String password) { + userProxy.createUser(username, password); + return Result.success("create user ok!"); + } + + /** + * Create a admin user only not exist admin user can use. + */ + @PostMapping("/admin") + public Object createAdminUser(@RequestParam(required = false) String password) { + + if (StringUtils.isBlank(password)) { + password = PasswordGeneratorUtil.generateRandomPassword(); + } + return userProxy.createAdminUser(password); + } + + /** + * Delete an existed user. + * + * @param username username of user + * @return ok if deleted succeed, keep silent if user not exist + * @since 1.2.0 + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Object deleteUser(@RequestParam String username) { + userProxy.deleteUser(username); + return Result.success("delete user ok!"); + } + + /** + * Update an user. + * + * @param username username of user + * @param newPassword new password of user + * @param response http response + * @param request http request + * @return ok if update succeed + * @throws IllegalArgumentException if user not exist or oldPassword is incorrect + * @since 1.2.0 + */ + @PutMapping + @Secured(resource = AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE) + public Object updateUser(@RequestParam String username, @RequestParam String newPassword, + HttpServletResponse response, HttpServletRequest request) throws IOException { + userProxy.updateUser(username, newPassword, response, request); + return Result.success("update user ok!"); + + } + + + /** + * Get paged users with the option for accurate or fuzzy search. + * + * @param pageNo number index of page + * @param pageSize size of page + * @param username the username to search for, can be an empty string + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return A collection of users, empty set if no user is found + * @since 1.2.0 + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ) + public Result> getUserList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "username", required = false, defaultValue = "") String username, + @RequestParam(name = "search", required = false, defaultValue = "accurate") String search) { + Page userPage = userProxy.getUserList(pageNo, pageSize, username, search); + return Result.success(userPage); + } + + /** + * Fuzzy matching username. + * + * @param username username + * @return Matched username + */ + @GetMapping("/search") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Result> getUserListByUsername(@RequestParam String username) { + List userList = userProxy.getUserListByUsername(username); + return Result.success(userList); + } + + /** + * Login to Nacos + * + *

This methods uses username and password to require a new token. + * + * @param username username of user + * @param password password + * @param response http response + * @param request http request + * @return new token of the user + * @throws AccessException if user info is incorrect + */ + @PostMapping("/login") + public Object login(@RequestParam String username, @RequestParam String password, HttpServletResponse response, + HttpServletRequest request) throws AccessException, IOException { + Object loginResult = userProxy.login(username, password, response, request); + return Result.success(loginResult); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java new file mode 100644 index 00000000000..b14f8040061 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.auth; + +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; + +/** + * Interface for handling permission-related operations. + * + * @author zhangyukun + */ +public interface PermissionHandler { + + /** + * Add a permission to a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + */ + boolean createPermission(String role, String resource, String action); + + /** + * Delete a permission from a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + */ + boolean deletePermission(String role, String resource, String action); + + /** + * Get a paginated list of permissions for a role with the option for accurate or fuzzy search. + * + * @param role the role + * @param pageNo the page number + * @param pageSize the size of the page + * @param search the type of search: "accurate" or "blur" + * @return a paginated list of permissions + */ + Page getPermissionList(String role, int pageNo, int pageSize, String search); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java new file mode 100644 index 00000000000..c7219718e40 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.auth; + +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; + +import java.util.List; + +/** + * Interface for handling role-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +public interface RoleHandler { + + /** + * Add a role to a user or create a role and bind it to GLOBAL_ADMIN. + * + * @param role the role name + * @param username the username + * @return true if the operation is successful + */ + boolean createRole(String role, String username); + + /** + * Delete a role or delete all users under this role if no username is specified. + * + * @param role the role name + * @param username the username (optional) + * @return true if the operation is successful + */ + boolean deleteRole(String role, String username); + + /** + * Get a paginated list of roles with the option for accurate or fuzzy search. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param username the username (optional) + * @param role the role name (optional) + * @param search the type of search: "accurate" or "blur" + * @return a paginated list of roles + */ + Page getRoleList(int pageNo, int pageSize, String username, String role, String search); + + /** + * Fuzzy match a role name. + * + * @param role the role name + * @return a list of matching roles + */ + List getRoleListByRoleName(String role); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java new file mode 100644 index 00000000000..ba61ad7a2e7 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.auth; + +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * Interface for handling user-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +public interface UserHandler { + + /** + * Create a new user. + * + * @param username the username + * @param password the password + * @return true if the user is created successfully + * @throws IllegalArgumentException if the user already exists + */ + boolean createUser(String username, String password); + + /** + * Create an admin user. This operation can only be used if no admin user exists. + * + * @param password the password for the admin user + * @return the result of the operation as a boolean or other data structure + */ + Object createAdminUser(String password); + + /** + * Delete an existing user. + * + * @param username the username of the user to be deleted + * @return true if the user is deleted successfully + * @throws IllegalArgumentException if trying to delete an admin user + */ + boolean deleteUser(String username); + + /** + * Update a user's password. + * + * @param username the username of the user + * @param newPassword the new password + * @param response the HTTP response + * @param request the HTTP request + * @return true if the password is updated successfully + * @throws IOException if an I/O error occurs + */ + Object updateUser(String username, String newPassword, HttpServletResponse response, HttpServletRequest request) throws IOException; + + /** + * Get a list of users with pagination and optional accurate or fuzzy search. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param username the username to search for + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return a paginated list of users + */ + Page getUserList(int pageNo, int pageSize, String username, String search); + + /** + * Fuzzy match a username. + * + * @param username the username to match + * @return a list of matched usernames + */ + List getUserListByUsername(String username); + + /** + * Login to Nacos. + * + * @param username the username + * @param password the password + * @param response the HTTP response + * @param request the HTTP request + * @return a result object containing login information + * @throws AccessException if user credentials are incorrect + * @throws IOException if an I/O error occurs + */ + Object login(String username, String password, HttpServletResponse response, HttpServletRequest request) throws AccessException, IOException; +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java new file mode 100644 index 00000000000..091006f03dd --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.inner.auth; + +import com.alibaba.nacos.console.handler.auth.PermissionHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.springframework.stereotype.Service; + +/** + * Implementation of PermissionHandler that handles permission-related operations. + * + * @author zhangyukun + */ +@Service +public class PermissionInnerHandler implements PermissionHandler { + + private final NacosRoleServiceImpl nacosRoleService; + + /** + * Constructs a new PermissionInnerHandler with the provided dependencies. + * + * @param nacosRoleService the service for role-related operations + */ + public PermissionInnerHandler(NacosRoleServiceImpl nacosRoleService) { + this.nacosRoleService = nacosRoleService; + } + + /** + * Adds a permission to a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + */ + @Override + public boolean createPermission(String role, String resource, String action) { + nacosRoleService.addPermission(role, resource, action); + return true; + } + + /** + * Deletes a permission from a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + */ + @Override + public boolean deletePermission(String role, String resource, String action) { + nacosRoleService.deletePermission(role, resource, action); + return true; + } + + /** + * Retrieves a paginated list of permissions for a role with the option for accurate or fuzzy search. + * + * @param role the role + * @param pageNo the page number + * @param pageSize the size of the page + * @param search the type of search: "accurate" or "blur" + * @return a paginated list of permissions + */ + @Override + public Page getPermissionList(String role, int pageNo, int pageSize, String search) { + if ("blur".equalsIgnoreCase(search)) { + return nacosRoleService.findPermissionsLike4Page(role, pageNo, pageSize); + } else { + return nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize); + } + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java new file mode 100644 index 00000000000..283d5976dc0 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.inner.auth; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.handler.auth.RoleHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.springframework.stereotype.Service; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; + +import java.util.List; + +/** + * Implementation of RoleHandler that handles role-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class RoleInnerHandler implements RoleHandler { + + private final NacosRoleServiceImpl roleService; + + public RoleInnerHandler(NacosRoleServiceImpl roleService) { + this.roleService = roleService; + } + + @Override + public boolean createRole(String role, String username) { + roleService.addRole(role, username); + return true; + } + + @Override + public boolean deleteRole(String role, String username) { + if (StringUtils.isBlank(username)) { + roleService.deleteRole(role); + } else { + roleService.deleteRole(role, username); + } + return true; + } + + @Override + public Page getRoleList(int pageNo, int pageSize, String username, String role, String search) { + if ("blur".equalsIgnoreCase(search)) { + return roleService.findRolesLike4Page(username, role, pageNo, pageSize); + } else { + return roleService.getRolesFromDatabase(username, role, pageNo, pageSize); + } + } + + @Override + public List getRoleListByRoleName(String role) { + return roleService.findRolesLikeRoleName(role); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java new file mode 100644 index 00000000000..242be922f27 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java @@ -0,0 +1,240 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.handler.inner.auth; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.console.handler.auth.UserHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.utils.PasswordEncoderUtil; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.web.HttpSessionRequiredException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * Implementation of UserHandler that handles user-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class UserInnerHandler implements UserHandler { + + private final NacosUserDetailsServiceImpl userDetailsService; + + private final NacosRoleServiceImpl roleService; + + private final AuthConfigs authConfigs; + + private final IAuthenticationManager iAuthenticationManager; + + private final TokenManagerDelegate jwtTokenManager; + + private final AuthenticationManager authenticationManager; + + /** + * Constructs a new UserInnerHandler with the provided dependencies. + * + * @param userDetailsService the service for user details operations + * @param roleService the service for role operations + * @param authConfigs the authentication configuration + * @param iAuthenticationManager the authentication manager interface + * @param jwtTokenManager the JWT token manager + * @param authenticationManager the authentication manager + */ + public UserInnerHandler(NacosUserDetailsServiceImpl userDetailsService, NacosRoleServiceImpl roleService, + AuthConfigs authConfigs, IAuthenticationManager iAuthenticationManager, + TokenManagerDelegate jwtTokenManager, @Deprecated AuthenticationManager authenticationManager) { + this.userDetailsService = userDetailsService; + this.roleService = roleService; + this.authConfigs = authConfigs; + this.iAuthenticationManager = iAuthenticationManager; + this.jwtTokenManager = jwtTokenManager; + this.authenticationManager = authenticationManager; + } + + @Override + public boolean createUser(String username, String password) { + User user = userDetailsService.getUserFromDatabase(username); + if (user != null) { + throw new IllegalArgumentException("user '" + username + "' already exist!"); + } + userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); + return true; + } + + @Override + public Object createAdminUser(String password) { + + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + if (iAuthenticationManager.hasGlobalAdminRole()) { + return Result.failure(ErrorCode.CONFLICT, "have admin user cannot use it"); + } + String username = AuthConstants.DEFAULT_USER; + userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); + roleService.addAdminRole(username); + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + result.put(AuthConstants.PARAM_USERNAME, username); + result.put(AuthConstants.PARAM_PASSWORD, password); + return Result.success(result); + } else { + return Result.failure(ErrorCode.NOT_IMPLEMENTED, "not support"); + } + } + + @Override + public boolean deleteUser(String username) { + List roleInfoList = roleService.getRoles(username); + if (roleInfoList != null) { + for (RoleInfo roleInfo : roleInfoList) { + if (AuthConstants.GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) { + throw new IllegalArgumentException("cannot delete admin: " + username); + } + } + } + userDetailsService.deleteUser(username); + return true; + } + + @Override + public Object updateUser(String username, String newPassword, HttpServletResponse response, + HttpServletRequest request) throws IOException { + try { + if (!hasPermission(username, request)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "authorization failed!"); + return null; + } + } catch (HttpSessionRequiredException e) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "session expired!"); + return null; + } catch (AccessException exception) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "authorization failed!"); + return null; + } + + User user = userDetailsService.getUserFromDatabase(username); + if (user == null) { + throw new IllegalArgumentException("user " + username + " not exist!"); + } + + userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); + return "update user ok!"; + } + + private boolean hasPermission(String username, HttpServletRequest request) + throws HttpSessionRequiredException, AccessException { + if (!authConfigs.isAuthEnabled()) { + return true; + } + IdentityContext identityContext = (IdentityContext) request.getSession() + .getAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_CONTEXT); + if (identityContext == null) { + throw new HttpSessionRequiredException("session expired!"); + } + NacosUser user = (NacosUser) identityContext.getParameter(AuthConstants.NACOS_USER_KEY); + if (user == null) { + user = iAuthenticationManager.authenticate(request); + if (user == null) { + throw new HttpSessionRequiredException("session expired!"); + } + //get user form jwt need check permission + iAuthenticationManager.hasGlobalAdminRole(user); + } + // admin + if (user.isGlobalAdmin()) { + return true; + } + // same user + return user.getUserName().equals(username); + } + + @Override + public Page getUserList(int pageNo, int pageSize, String username, String search) { + if ("blur".equalsIgnoreCase(search)) { + return userDetailsService.findUsersLike4Page(username, pageNo, pageSize); + } else { + return userDetailsService.getUsersFromDatabase(pageNo, pageSize, username); + } + } + + @Override + public List getUserListByUsername(String username) { + return userDetailsService.findUserLikeUsername(username); + } + + @Override + public Object login(String username, String password, HttpServletResponse response, HttpServletRequest request) + throws AccessException, IOException { + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType()) + || AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + + NacosUser user = iAuthenticationManager.authenticate(request); + + response.addHeader(AuthConstants.AUTHORIZATION_HEADER, AuthConstants.TOKEN_PREFIX + user.getToken()); + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + result.put(Constants.ACCESS_TOKEN, user.getToken()); + result.put(Constants.TOKEN_TTL, jwtTokenManager.getTokenTtlInSeconds(user.getToken())); + result.put(Constants.GLOBAL_ADMIN, iAuthenticationManager.hasGlobalAdminRole(user)); + result.put(Constants.USERNAME, user.getUserName()); + return result; + } + + // create Authentication class through username and password, the implement class is UsernamePasswordAuthenticationToken + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, + password); + + try { + // use the method authenticate of AuthenticationManager(default implement is ProviderManager) to valid Authentication + Authentication authentication = authenticationManager.authenticate(authenticationToken); + // bind SecurityContext to Authentication + SecurityContextHolder.getContext().setAuthentication(authentication); + // generate Token + String token = jwtTokenManager.createToken(authentication); + // write Token to Http header + response.addHeader(AuthConstants.AUTHORIZATION_HEADER, "Bearer " + token); + return Result.success("Bearer " + token); + } catch (BadCredentialsException authentication) { + return Result.failure(ErrorCode.UNAUTHORIZED, "Login failed"); + } + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java new file mode 100644 index 00000000000..998f34685df --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.proxy.auth; + +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.auth.PermissionHandler; +import com.alibaba.nacos.console.handler.inner.auth.PermissionInnerHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * Proxy class for handling permission-related operations. + * + * @author zhangyukun + */ +@Service +public class PermissionProxy { + + private final Map permissionHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new PermissionProxy with the given PermissionInnerHandler and ConsoleConfig. + * + * @param permissionInnerHandler the default implementation of PermissionHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + public PermissionProxy(PermissionInnerHandler permissionInnerHandler, ConsoleConfig consoleConfig) { + this.permissionHandlerMap.put("merged", permissionInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Adds a permission to a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + * @throws IllegalArgumentException if the deployment type is invalid + */ + public boolean createPermission(String role, String resource, String action) { + PermissionHandler permissionHandler = permissionHandlerMap.get(consoleConfig.getType()); + if (permissionHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return permissionHandler.createPermission(role, resource, action); + } + + /** + * Deletes a permission from a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if the operation was successful + * @throws IllegalArgumentException if the deployment type is invalid + */ + public boolean deletePermission(String role, String resource, String action) { + PermissionHandler permissionHandler = permissionHandlerMap.get(consoleConfig.getType()); + if (permissionHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return permissionHandler.deletePermission(role, resource, action); + } + + /** + * Retrieves a paginated list of permissions for a role with the option for accurate or fuzzy search. + * + * @param role the role + * @param pageNo the page number + * @param pageSize the size of the page + * @param search the type of search: "accurate" or "blur" + * @return a paginated list of permissions + * @throws IllegalArgumentException if the deployment type is invalid + */ + public Page getPermissionList(String role, int pageNo, int pageSize, String search) { + PermissionHandler permissionHandler = permissionHandlerMap.get(consoleConfig.getType()); + if (permissionHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return permissionHandler.getPermissionList(role, pageNo, pageSize, search); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java new file mode 100644 index 00000000000..f9d7401b060 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.proxy.auth; + +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.auth.RoleHandler; +import com.alibaba.nacos.console.handler.inner.auth.RoleInnerHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Proxy class for handling role-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class RoleProxy { + + private final Map roleHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new RoleProxy with the given RoleInnerHandler and ConsoleConfig. + * + * @param roleInnerHandler the default implementation of RoleHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + public RoleProxy(RoleInnerHandler roleInnerHandler, ConsoleConfig consoleConfig) { + this.roleHandlerMap.put("merged", roleInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Adds a role to a user or creates a role and binds it to GLOBAL_ADMIN. + * + * @param role the role name + * @param username the username + * @return true if the operation was successful + * @throws IllegalArgumentException if the deployment type is invalid + */ + public boolean createRole(String role, String username) { + RoleHandler roleHandler = roleHandlerMap.get(consoleConfig.getType()); + if (roleHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return roleHandler.createRole(role, username); + } + + /** + * Deletes a role or deletes all users under this role if no username is specified. + * + * @param role the role name + * @param username the username (optional) + * @return true if the operation was successful + * @throws IllegalArgumentException if the deployment type is invalid + */ + public boolean deleteRole(String role, String username) { + RoleHandler roleHandler = roleHandlerMap.get(consoleConfig.getType()); + if (roleHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return roleHandler.deleteRole(role, username); + } + + /** + * Retrieves a paginated list of roles with the option for accurate or fuzzy search. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param username the username (optional) + * @param role the role name (optional) + * @param search the type of search: "accurate" or "blur" + * @return a paginated list of roles + * @throws IllegalArgumentException if the deployment type is invalid + */ + public Page getRoleList(int pageNo, int pageSize, String username, String role, String search) { + RoleHandler roleHandler = roleHandlerMap.get(consoleConfig.getType()); + if (roleHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return roleHandler.getRoleList(pageNo, pageSize, username, role, search); + } + + /** + * Fuzzy matches a role name. + * + * @param role the role name + * @return a list of matching roles + * @throws IllegalArgumentException if the deployment type is invalid + */ + public List getRoleListByRoleName(String role) { + RoleHandler roleHandler = roleHandlerMap.get(consoleConfig.getType()); + if (roleHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return roleHandler.getRoleListByRoleName(role); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/UserProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/UserProxy.java new file mode 100644 index 00000000000..77de99a0555 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/auth/UserProxy.java @@ -0,0 +1,175 @@ +/* + * Copyright 1999-2024 Alibaba Group Holding Ltd. + * + * 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.alibaba.nacos.console.proxy.auth; + +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.auth.UserHandler; +import com.alibaba.nacos.console.handler.inner.auth.UserInnerHandler; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * roxy class for handling user-related operations. + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class UserProxy { + + private final Map userHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new UserProxy with the given UserInnerHandler and ConsoleConfig. + * + * @param userInnerHandler the default implementation of UserHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + @Autowired + public UserProxy(UserInnerHandler userInnerHandler, ConsoleConfig consoleConfig) { + this.userHandlerMap.put("merged", userInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Create a new user. + * + * @param username the username + * @param password the password + * @return a success message if the user is created + * @throws IllegalArgumentException if the user already exists + */ + public Object createUser(String username, String password) { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.createUser(username, password); + } + + /** + * Create an admin user. This operation can only be used if no admin user exists. + * + * @param password the password for the admin user + * @return the result of the operation + */ + public Object createAdminUser(String password) { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.createAdminUser(password); + } + + /** + * Delete an existing user. + * + * @param username the username of the user to be deleted + * @return a success message if the user is deleted, otherwise a silent response if the user does not exist + * @throws IllegalArgumentException if trying to delete an admin user + */ + public Object deleteUser(String username) { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.deleteUser(username); + } + + /** + * Update a user's password. + * + * @param username the username of the user + * @param newPassword the new password + * @param response the HTTP response + * @param request the HTTP request + * @return a success message if the password is updated + * @throws IOException if an I/O error occurs + */ + public Object updateUser(String username, String newPassword, HttpServletResponse response, + HttpServletRequest request) throws IOException { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.updateUser(username, newPassword, response, request); + } + + /** + * Get a list of users with pagination and optional accurate or fuzzy search. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param username the username to search for + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return a paginated list of users + */ + public Page getUserList(int pageNo, int pageSize, String username, String search) { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.getUserList(pageNo, pageSize, username, search); + } + + /** + * Fuzzy match a username. + * + * @param username the username to match + * @return a list of matched usernames + */ + public List getUserListByUsername(String username) { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.getUserListByUsername(username); + } + + /** + * Login to Nacos. + * + * @param username the username + * @param password the password + * @param response the HTTP response + * @param request the HTTP request + * @return a new token if the login is successful + * @throws AccessException if user credentials are incorrect + * @throws IOException if an I/O error occurs + */ + public Object login(String username, String password, HttpServletResponse response, HttpServletRequest request) + throws AccessException, IOException { + UserHandler userHandler = userHandlerMap.get(consoleConfig.getType()); + if (userHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return userHandler.login(username, password, response, request); + } +} +