From 6f524ec4b32c3d4231b2edd5515b9b0e920c422e 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:21:42 +0800 Subject: [PATCH 01/17] [ISSUE #12017] Add the console backend API for config section * Add history handling module --- .../v3/config/ConsoleHistoryController.java | 152 ++++++++++++++++++ .../handler/config/HistoryHandler.java | 83 ++++++++++ .../inner/config/HistoryInnerHandler.java | 86 ++++++++++ .../console/proxy/config/HistoryProxy.java | 131 +++++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/config/HistoryInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java new file mode 100644 index 00000000000..4e23dc72532 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java @@ -0,0 +1,152 @@ +/* + * 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.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.NamespaceUtil; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.console.proxy.config.HistoryProxy; +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.constant.SignType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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/cs/history") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleHistoryController { + + private final HistoryProxy historyProxy; + + @Autowired + public ConsoleHistoryController(HistoryProxy historyProxy) { + this.historyProxy = historyProxy; + } + + /** + * Query the detailed configuration history information. notes: + * + * @param nid history_config_info nid + * @param dataId dataId + * @param group groupId + * @param namespaceId namespaceId + * @return history config info + */ + @GetMapping + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result getConfigHistoryInfo(@RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, + @RequestParam("nid") Long nid) throws NacosException { + // check namespaceId + ParamUtils.checkTenantV2(namespaceId); + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + // check params + ParamUtils.checkParam(dataId, group, "datumId", "content"); + + return Result.success(historyProxy.getConfigHistoryInfo(dataId, group, namespaceId, nid)); + } + + /** + * Query the list history config. notes: + * + * @param dataId dataId string value [required]. + * @param group group string value [required]. + * @param namespaceId namespaceId. + * @param pageNo pageNo integer value. + * @param pageSize pageSize integer value. + * @return the page of history config. + */ + @GetMapping("/list") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result> listConfigHistory(@RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, + @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo, + @RequestParam(value = "pageSize", required = false, defaultValue = "100") Integer pageSize) + throws NacosException { + pageSize = Math.min(500, pageSize); + // check namespaceId + ParamUtils.checkTenantV2(namespaceId); + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + // check params + ParamUtils.checkParam(dataId, group, "datumId", "content"); + + return Result.success(historyProxy.listConfigHistory(dataId, group, namespaceId, pageNo, pageSize)); + } + + /** + * Query previous config history information. notes: + * + * @param id config_info id + * @param dataId dataId + * @param group groupId + * @param namespaceId namespaceId + * @return history config info + */ + @GetMapping(value = "/previous") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result getPreviousConfigHistoryInfo(@RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, + @RequestParam("id") Long id) throws NacosException { + // check namespaceId + ParamUtils.checkTenantV2(namespaceId); + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + // check params + ParamUtils.checkParam(dataId, group, "datumId", "content"); + + return Result.success(historyProxy.getPreviousConfigHistoryInfo(dataId, group, namespaceId, id)); + } + + /** + * Query configs list by namespace. + * + * @param namespaceId config_info namespace + * @return list + */ + @GetMapping(value = "/configs") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result> getConfigsByTenant(@RequestParam("namespaceId") String namespaceId) + throws NacosException { + // check namespaceId + ParamUtils.checkTenantV2(namespaceId); + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + + return Result.success(historyProxy.getConfigsByTenant(namespaceId)); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java new file mode 100644 index 00000000000..7e0db37aae9 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java @@ -0,0 +1,83 @@ +/* + * 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.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.persistence.model.Page; + +import java.util.List; + +/** + * Interface for handling configuration history related operations. + * + * @author zhangyukun + */ +public interface HistoryHandler { + + /** + * Query the detailed configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param nid the history record ID + * @return the detailed configuration history information + * @throws NacosException if any error occurs during the operation + */ + ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException; + + /** + * Query the list of configuration history. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the number of items per page + * @return the paginated list of configuration history + * @throws NacosException if any error occurs during the operation + */ + Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException; + + /** + * Query the previous configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param id the configuration ID + * @return the previous configuration history information + * @throws NacosException if any error occurs during the operation + */ + ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException; + + /** + * Query the list of configurations by namespace. + * + * @param namespaceId the namespace ID + * @return the list of configurations + * @throws NacosApiException if any error occurs during the operation + */ + List getConfigsByTenant(String namespaceId) throws NacosApiException; +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/HistoryInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/HistoryInnerHandler.java new file mode 100644 index 00000000000..e4ae925d94e --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/HistoryInnerHandler.java @@ -0,0 +1,86 @@ +/* + * 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.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.service.HistoryService; +import com.alibaba.nacos.console.handler.config.HistoryHandler; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class HistoryInnerHandler implements HistoryHandler { + + private final HistoryService historyService; + + @Autowired + public HistoryInnerHandler(HistoryService historyService) { + this.historyService = historyService; + } + + @Override + public ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException { + ConfigHistoryInfo configHistoryInfo; + try { + configHistoryInfo = historyService.getConfigHistoryInfo(dataId, group, namespaceId, nid); + } catch (DataAccessException e) { + throw new NacosApiException(HttpStatus.NOT_FOUND.value(), ErrorCode.RESOURCE_NOT_FOUND, + "certain config history for nid = " + nid + " not exist"); + } + return configHistoryInfo; + } + + @Override + public Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException { + return historyService.listConfigHistory(dataId, group, namespaceId, pageNo, pageSize); + } + + @Override + public ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException { + ConfigHistoryInfo configHistoryInfo; + try { + configHistoryInfo = historyService.getPreviousConfigHistoryInfo(dataId, group, namespaceId, id); + } catch (DataAccessException e) { + throw new NacosApiException(HttpStatus.NOT_FOUND.value(), ErrorCode.RESOURCE_NOT_FOUND, + "previous config history for id = " + id + " not exist"); + } + return configHistoryInfo; + } + + @Override + public List getConfigsByTenant(String namespaceId) throws NacosApiException { + return historyService.getConfigListByNamespace(namespaceId); + } +} \ No newline at end of file diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java new file mode 100644 index 00000000000..91c361f6869 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java @@ -0,0 +1,131 @@ +/* + * 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.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.config.HistoryHandler; +import com.alibaba.nacos.console.handler.inner.config.HistoryInnerHandler; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class HistoryProxy { + + private final Map historyHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new HistoryProxy with the given HistoryInnerHandler and ConsoleConfig. + * + * @param historyInnerHandler the default implementation of HistoryHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + @Autowired + public HistoryProxy(HistoryInnerHandler historyInnerHandler, ConsoleConfig consoleConfig) { + this.historyHandlerMap.put("merged", historyInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Query the detailed configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param nid the history record ID + * @return the detailed configuration history information + * @throws NacosException if any error occurs during the operation + */ + public ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException { + HistoryHandler historyHandler = historyHandlerMap.get(consoleConfig.getType()); + if (historyHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return historyHandler.getConfigHistoryInfo(dataId, group, namespaceId, nid); + } + + /** + * Query the list of configuration history. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the number of items per page + * @return the paginated list of configuration history + * @throws NacosException if any error occurs during the operation + */ + public Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException { + HistoryHandler historyHandler = historyHandlerMap.get(consoleConfig.getType()); + if (historyHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return historyHandler.listConfigHistory(dataId, group, namespaceId, pageNo, pageSize); + } + + /** + * Query the previous configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param id the configuration ID + * @return the previous configuration history information + * @throws NacosException if any error occurs during the operation + */ + public ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException { + HistoryHandler historyHandler = historyHandlerMap.get(consoleConfig.getType()); + if (historyHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return historyHandler.getPreviousConfigHistoryInfo(dataId, group, namespaceId, id); + } + + /** + * Query the list of configurations by namespace. + * + * @param namespaceId the namespace ID + * @return the list of configurations + * @throws NacosApiException if any error occurs during the operation + */ + public List getConfigsByTenant(String namespaceId) throws NacosException { + HistoryHandler historyHandler = historyHandlerMap.get(consoleConfig.getType()); + if (historyHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return historyHandler.getConfigsByTenant(namespaceId); + } +} From 0ad172f95cd896477c4576cb1a5d31691f78b125 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:28:57 +0800 Subject: [PATCH 02/17] [ISSUE #12017] Add the console backend API for naming section * Add service handling module * Add instance handling module --- .../v3/naming/ConsoleInstanceController.java | 136 +++++++++++ .../v3/naming/ConsoleServiceController.java | 230 ++++++++++++++++++ .../inner/naming/InstanceInnerHandler.java | 137 +++++++++++ .../inner/naming/ServiceInnerHandler.java | 148 +++++++++++ .../handler/naming/InstanceHandler.java | 56 +++++ .../handler/naming/ServiceHandler.java | 114 +++++++++ .../console/proxy/naming/InstanceProxy.java | 94 +++++++ .../console/proxy/naming/ServiceProxy.java | 180 ++++++++++++++ 8 files changed, 1095 insertions(+) create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/InstanceInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java new file mode 100644 index 00000000000..ed1a376cf78 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java @@ -0,0 +1,136 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.builder.InstanceBuilder; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.naming.InstanceProxy; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.alibaba.nacos.naming.web.CanDistro; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; +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; + +/** + * Controller for handling HTTP requests related to instance operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/ns/instance") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleInstanceController { + + private final SwitchDomain switchDomain; + + private final InstanceProxy instanceProxy; + + /** + * Constructs a new ConsoleInstanceController with the provided InstanceProxy. + * + * @param instanceProxy the proxy used for handling instance-related operations + */ + public ConsoleInstanceController(InstanceProxy instanceProxy, SwitchDomain switchDomain) { + this.instanceProxy = instanceProxy; + this.switchDomain = switchDomain; + } + + /** + * List instances of special service. + * + * @param namespaceId namespace id + * @param serviceName service name + * @param healthyOnly instance health only + * @param enabledOnly instance enabled + * @param page number of page + * @param pageSize size of each page + * @return instances information + */ + @Secured(action = ActionTypes.READ) + @RequestMapping("/list") + public Result getInstanceList( + @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, + @RequestParam String serviceName, @RequestParam(required = false) Boolean healthyOnly, + @RequestParam(required = false) Boolean enabledOnly, @RequestParam(name = "pageNo") int page, + @RequestParam int pageSize) { + String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); + String groupName = NamingUtils.getGroupName(serviceName); + ObjectNode result = instanceProxy.listInstances(namespaceId, serviceNameWithoutGroup, groupName, page, pageSize, healthyOnly, enabledOnly); + return Result.success(result); + } + + /** + * Update instance. + */ + @CanDistro + @PutMapping + @TpsControl(pointName = "NamingInstanceUpdate", name = "HttpNamingInstanceUpdate") + @Secured(action = ActionTypes.WRITE) + public Result updateInstance(InstanceForm instanceForm) throws NacosException { + // check param + instanceForm.validate(); + checkWeight(instanceForm.getWeight()); + // build instance + Instance instance = buildInstance(instanceForm); + instanceProxy.updateInstance(instanceForm, instance); + return Result.success("ok"); + } + + private void checkWeight(Double weight) throws NacosException { + if (weight > com.alibaba.nacos.naming.constants.Constants.MAX_WEIGHT_VALUE + || weight < com.alibaba.nacos.naming.constants.Constants.MIN_WEIGHT_VALUE) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.WEIGHT_ERROR, + "instance format invalid: The weights range from " + + com.alibaba.nacos.naming.constants.Constants.MIN_WEIGHT_VALUE + " to " + + com.alibaba.nacos.naming.constants.Constants.MAX_WEIGHT_VALUE); + } + } + + private Instance buildInstance(InstanceForm instanceForm) throws NacosException { + Instance instance = InstanceBuilder.newBuilder().setServiceName(buildCompositeServiceName(instanceForm)) + .setIp(instanceForm.getIp()).setClusterName(instanceForm.getClusterName()) + .setPort(instanceForm.getPort()).setHealthy(instanceForm.getHealthy()) + .setWeight(instanceForm.getWeight()).setEnabled(instanceForm.getEnabled()) + .setMetadata(UtilsAndCommons.parseMetadata(instanceForm.getMetadata())) + .setEphemeral(instanceForm.getEphemeral()).build(); + if (instanceForm.getEphemeral() == null) { + instance.setEphemeral((switchDomain.isDefaultInstanceEphemeral())); + } + return instance; + } + + private String buildCompositeServiceName(InstanceForm instanceForm) { + return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java new file mode 100644 index 00000000000..15e2000f016 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -0,0 +1,230 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.CommonParams; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.api.selector.Selector; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.NumberUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.naming.ServiceProxy; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.core.utils.WebUtils; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.selector.NoneSelector; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +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 java.net.URLDecoder; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Controller for handling HTTP requests related to service operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/ns/service") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleServiceController { + + private final ServiceProxy serviceProxy; + + private final SelectorManager selectorManager; + + public ConsoleServiceController(ServiceProxy serviceProxy, SelectorManager selectorManager) { + this.serviceProxy = serviceProxy; + this.selectorManager = selectorManager; + } + + /** + * Create a new service. This API will create a persistence service. + */ + @PostMapping() + @TpsControl(pointName = "NamingServiceRegister", name = "HttpNamingServiceRegister") + @Secured(action = ActionTypes.WRITE) + public Result createService(ServiceForm serviceForm) throws Exception { + serviceForm.validate(); + ServiceMetadata serviceMetadata = new ServiceMetadata(); + serviceMetadata.setProtectThreshold(serviceForm.getProtectThreshold()); + serviceMetadata.setSelector(parseSelector(serviceForm.getSelector())); + serviceMetadata.setExtendData(UtilsAndCommons.parseMetadata(serviceForm.getMetadata())); + serviceMetadata.setEphemeral(serviceForm.getEphemeral()); + + serviceProxy.createService(serviceForm, serviceMetadata); + return Result.success("ok"); + } + + /** + * Remove service. + */ + @DeleteMapping() + @TpsControl(pointName = "NamingServiceDeregister", name = "HttpNamingServiceDeregister") + @Secured(action = ActionTypes.WRITE) + public Result deleteService( + @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, + @RequestParam("serviceName") String serviceName, + @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName) + throws Exception { + serviceProxy.deleteService(namespaceId, serviceName, groupName); + return Result.success("ok"); + } + + /** + * Update service. + */ + @PutMapping() + @TpsControl(pointName = "NamingServiceUpdate", name = "HttpNamingServiceUpdate") + @Secured(action = ActionTypes.WRITE) + public Result updateService(ServiceForm serviceForm) throws Exception { + serviceForm.validate(); + + Map metadata = UtilsAndCommons.parseMetadata(serviceForm.getMetadata()); + ServiceMetadata serviceMetadata = new ServiceMetadata(); + serviceMetadata.setProtectThreshold(serviceForm.getProtectThreshold()); + serviceMetadata.setExtendData(metadata); + serviceMetadata.setSelector(parseSelector(serviceForm.getSelector())); + Service service = Service.newService(serviceForm.getNamespaceId(), serviceForm.getGroupName(), + serviceForm.getServiceName()); + + serviceProxy.updateService(serviceForm, service, serviceMetadata, metadata); + return Result.success("ok"); + } + + /** + * Get all {@link Selector} types. + * + * @return {@link Selector} types. + */ + @GetMapping("/selector/types") + public Result> getSelectorTypeList() { + return Result.success(serviceProxy.getSelectorTypeList()); + } + + /** + * get subscriber list. + * + * @param request http request + * @return Jackson object node + */ + @GetMapping("/subscribers") + @Secured(action = ActionTypes.READ) + public Result subscribers(HttpServletRequest request) throws Exception { + + int pageNo = NumberUtils.toInt(WebUtils.optional(request, "pageNo", "1")); + int pageSize = NumberUtils.toInt(WebUtils.optional(request, "pageSize", "1000")); + + String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); + String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); + boolean aggregation = Boolean.parseBoolean( + WebUtils.optional(request, "aggregation", String.valueOf(Boolean.TRUE))); + + return Result.success(serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation)); + //下放参数 pageNo, pageSize, namespaceId, serviceName, aggregation + + } + + private Selector parseSelector(String selectorJsonString) throws Exception { + if (StringUtils.isBlank(selectorJsonString)) { + return new NoneSelector(); + } + + JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8")); + String type = Optional.ofNullable(selectorJson.get("type")).orElseThrow( + () -> new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SELECTOR_ERROR, + "not match any type of selector!")).asText(); + String expression = Optional.ofNullable(selectorJson.get("expression")).map(JsonNode::asText).orElse(null); + Selector selector = selectorManager.parseSelector(type, expression); + if (Objects.isNull(selector)) { + throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SELECTOR_ERROR, + "not match any type of selector!"); + } + return selector; + } + + + /** + * List service detail information. + * + * @param withInstances whether return instances + * @param namespaceId namespace id + * @param pageNo number of page + * @param pageSize size of each page + * @param serviceName service name + * @param groupName group name + * @param containedInstance instance name pattern which will be contained in detail + * @param hasIpCount whether filter services with empty instance + * @return list service detail + */ + @Secured(action = ActionTypes.READ) + @GetMapping("/list") + public Object getServiceList(@RequestParam(required = false) boolean withInstances, + @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, + @RequestParam(required = false) int pageNo, @RequestParam(required = false) int pageSize, + @RequestParam(name = "serviceNameParam", defaultValue = StringUtils.EMPTY) String serviceName, + @RequestParam(name = "groupNameParam", defaultValue = StringUtils.EMPTY) String groupName, + @RequestParam(name = "instance", defaultValue = StringUtils.EMPTY) String containedInstance, + @RequestParam(required = false) boolean hasIpCount) throws NacosException { + return Result.success( + serviceProxy.getServiceList(withInstances, namespaceId, pageNo, pageSize, serviceName, groupName, + containedInstance, hasIpCount)); + } + + /** + * Get service detail. + * + * @param namespaceId namespace id + * @param serviceName service name + * @return service detail information + * @throws NacosException nacos exception + */ + @Secured(action = ActionTypes.READ) + @GetMapping() + public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, + String serviceName) throws NacosException { + String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); + String groupName = NamingUtils.getGroupName(serviceName); + return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName)); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/InstanceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/InstanceInnerHandler.java new file mode 100644 index 00000000000..961bb685e91 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/InstanceInnerHandler.java @@ -0,0 +1,137 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.trace.event.naming.UpdateInstanceTraceEvent; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.console.handler.naming.InstanceHandler; +import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; +import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.alibaba.nacos.naming.model.form.InstanceMetadataBatchOperationForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Implementation of InstanceHandler that handles instance-related operations. + * + * @author zhangyukun + */ +@Service +public class InstanceInnerHandler implements InstanceHandler { + + private final CatalogServiceV2Impl catalogServiceV2; + + private final InstanceOperatorClientImpl instanceServiceV2; + + /** + * Constructs a new InstanceInnerHandler with the provided dependencies. + * + * @param catalogServiceV2 the service for catalog-related operations + */ + @Autowired + public InstanceInnerHandler(CatalogServiceV2Impl catalogServiceV2, InstanceOperatorClientImpl instanceServiceV2) { + this.catalogServiceV2 = catalogServiceV2; + this.instanceServiceV2 = instanceServiceV2; + } + + /** + * Retrieves a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + */ + @Override + public ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, int page, + int pageSize, Boolean healthyOnly, Boolean enabledOnly) { + List instances = catalogServiceV2.listAllInstances(namespaceId, groupName, + serviceNameWithoutGroup); + int start = (page - 1) * pageSize; + + if (start < 0) { + start = 0; + } + int end = start + pageSize; + + if (start > instances.size()) { + start = instances.size(); + } + + if (end > instances.size()) { + end = instances.size(); + } + + Stream stream = instances.stream(); + if (healthyOnly != null) { + stream = stream.filter(instance -> instance.isHealthy() == healthyOnly); + } + if (enabledOnly != null) { + stream = stream.filter(i -> i.isEnabled() == enabledOnly); + } + List ins = stream.collect(Collectors.toList()); + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + if (ins.size() > start) { + result.replace("instances", JacksonUtils.transferToJsonNode(ins.subList(start, end))); + } + result.put("count", ins.size()); + + return result; + } + + /** + * Updates an instance. + * + * @param instanceForm the instanceForm + * @param instance the instance to update + * @throws NacosException if the update operation fails + */ + @Override + public void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException { + instanceServiceV2.updateInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), + instance); + NotifyCenter.publishEvent( + new UpdateInstanceTraceEvent(System.currentTimeMillis(), "", instanceForm.getNamespaceId(), + instanceForm.getGroupName(), instanceForm.getServiceName(), instance.getIp(), + instance.getPort(), instance.getMetadata())); + } + + private String buildCompositeServiceName(InstanceForm instanceForm) { + return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); + } + + private String buildCompositeServiceName(InstanceMetadataBatchOperationForm form) { + return NamingUtils.getGroupedName(form.getServiceName(), form.getGroupName()); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java new file mode 100644 index 00000000000..8b7e6df08db --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java @@ -0,0 +1,148 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.trace.event.naming.DeregisterServiceTraceEvent; +import com.alibaba.nacos.common.trace.event.naming.RegisterServiceTraceEvent; +import com.alibaba.nacos.common.trace.event.naming.UpdateServiceTraceEvent; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.console.handler.naming.ServiceHandler; +import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; +import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl; +import com.alibaba.nacos.naming.core.SubscribeManager; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.pojo.Subscriber; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * Implementation of ServiceHandler that handles service-related operations. + * + * @author zhangyukun + */ +@Service +public class ServiceInnerHandler implements ServiceHandler { + + private final ServiceOperatorV2Impl serviceOperatorV2; + + private final SelectorManager selectorManager; + + private final CatalogServiceV2Impl catalogServiceV2; + + private final SubscribeManager subscribeManager; + + @Autowired + public ServiceInnerHandler(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager, + CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager) { + this.serviceOperatorV2 = serviceOperatorV2; + this.selectorManager = selectorManager; + this.catalogServiceV2 = catalogServiceV2; + this.subscribeManager = subscribeManager; + } + + @Override + public void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception { + serviceOperatorV2.create(com.alibaba.nacos.naming.core.v2.pojo.Service.newService(serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName(), serviceForm.getEphemeral()), serviceMetadata); + NotifyCenter.publishEvent( + new RegisterServiceTraceEvent(System.currentTimeMillis(), serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName())); + } + + @Override + public void deleteService(String namespaceId, String serviceName, String groupName) throws Exception { + serviceOperatorV2.delete( + com.alibaba.nacos.naming.core.v2.pojo.Service.newService(namespaceId, groupName, serviceName)); + NotifyCenter.publishEvent( + new DeregisterServiceTraceEvent(System.currentTimeMillis(), namespaceId, groupName, serviceName)); + } + + @Override + public void updateService(ServiceForm serviceForm, com.alibaba.nacos.naming.core.v2.pojo.Service service, + ServiceMetadata serviceMetadata, Map metadata) throws Exception { + serviceOperatorV2.update(service, serviceMetadata); + NotifyCenter.publishEvent(new UpdateServiceTraceEvent(System.currentTimeMillis(), serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName(), metadata)); + } + + @Override + public List getSelectorTypeList() { + return selectorManager.getAllSelectorTypes(); + } + + @Override + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + boolean aggregation) throws Exception { + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + + int count = 0; + + try { + List subscribers = subscribeManager.getSubscribers(serviceName, namespaceId, aggregation); + + int start = (pageNo - 1) * pageSize; + if (start < 0) { + start = 0; + } + + int end = start + pageSize; + count = subscribers.size(); + if (end > count) { + end = count; + } + + result.replace("subscribers", JacksonUtils.transferToJsonNode(subscribers.subList(start, end))); + result.put("count", count); + + return result; + } catch (Exception e) { + Loggers.SRV_LOG.warn("query subscribers failed!", e); + result.replace("subscribers", JacksonUtils.createEmptyArrayNode()); + result.put("count", count); + return result; + } + } + + @Override + public Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, + String serviceName, String groupName, String containedInstance, boolean hasIpCount) throws NacosException { + if (withInstances) { + return catalogServiceV2.pageListServiceDetail(namespaceId, groupName, serviceName, pageNo, pageSize); + } + return catalogServiceV2.pageListService(namespaceId, groupName, serviceName, pageNo, pageSize, + containedInstance, hasIpCount); + } + + @Override + public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) + throws NacosException { + return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceNameWithoutGroup); + } + +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java new file mode 100644 index 00000000000..91b27803ba3 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java @@ -0,0 +1,56 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Interface for handling instance-related operations. + * + * @author zhangyukun + */ +public interface InstanceHandler { + + /** + * Retrieve a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + */ + ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, + int page, int pageSize, Boolean healthyOnly, Boolean enabledOnly); + + /** + * Update an instance. + * + * @param instanceForm the form containing instance data + * @param instance the instance to update + * @throws NacosException if the update operation fails + */ + void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException; +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java new file mode 100644 index 00000000000..e1cb6042464 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java @@ -0,0 +1,114 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.List; +import java.util.Map; + +/** + * Interface for handling service-related operations. + * + * @author zhangyukun + */ +public interface ServiceHandler { + + /** + * Create a new service. + * + * @param serviceForm the service form containing the service details + * @throws Exception if an error occurs during service creation + */ + void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception; + + /** + * Delete an existing service. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @throws Exception if an error occurs during service deletion + */ + void deleteService(String namespaceId, String serviceName, String groupName) throws Exception; + + /** + * Update an existing service. + * + * @param serviceForm the service form containing the service details + * @param service the service object created from serviceForm + * @param serviceMetadata the service metadata created from serviceForm + * @throws Exception if an error occurs during service update + */ + void updateService(ServiceForm serviceForm, Service service, ServiceMetadata serviceMetadata, + Map metadata) throws Exception; + + /** + * Get all selector types. + * + * @return a list of selector types + */ + List getSelectorTypeList(); + + /** + * Get the list of subscribers for a service. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param aggregation whether to aggregate the results + * @return a JSON node containing the list of subscribers + * @throws Exception if an error occurs during fetching subscribers + */ + ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, boolean aggregation) + throws Exception; + + /** + * List service detail information. + * + * @param withInstances whether to include instances + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the size of the page + * @param serviceName the service name + * @param groupName the group name + * @param containedInstance instance name pattern which will be contained in detail + * @param hasIpCount whether to filter services with empty instances + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, String serviceName, + String groupName, String containedInstance, boolean hasIpCount) throws NacosException; + + /** + * Get the detail of a specific service. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) throws NacosException; +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java new file mode 100644 index 00000000000..d7c4de45d9d --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java @@ -0,0 +1,94 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.inner.naming.InstanceInnerHandler; +import com.alibaba.nacos.console.handler.naming.InstanceHandler; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * Proxy class for handling instance-related operations. + * + * @author zhangyukun + */ +@Service +public class InstanceProxy { + + private final Map instanceHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new InstanceProxy with the given InstanceInnerHandler and ConsoleConfig. + * + * @param instanceInnerHandler the default implementation of InstanceHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + public InstanceProxy(InstanceInnerHandler instanceInnerHandler, ConsoleConfig consoleConfig) { + this.instanceHandlerMap.put("merged", instanceInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Retrieve a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + * @throws IllegalArgumentException if the deployment type is invalid + */ + public ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, int page, + int pageSize, Boolean healthyOnly, Boolean enabledOnly) { + InstanceHandler instanceHandler = instanceHandlerMap.get(consoleConfig.getType()); + if (instanceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return instanceHandler.listInstances(namespaceId, serviceNameWithoutGroup, groupName, page, pageSize, + healthyOnly, enabledOnly); + } + + /** + * Updates an instance. + * + * @param instanceForm the form containing instance data + * @param instance the instance to update + * @throws NacosException if the update operation fails + * @throws IllegalArgumentException if the deployment type is invalid + */ + public void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException { + InstanceHandler instanceHandler = instanceHandlerMap.get(consoleConfig.getType()); + if (instanceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + instanceHandler.updateInstance(instanceForm, instance); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java new file mode 100644 index 00000000000..28e17c46e00 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -0,0 +1,180 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.inner.naming.ServiceInnerHandler; +import com.alibaba.nacos.console.handler.naming.ServiceHandler; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; + +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Proxy class for handling service-related operations. + * + * @author zhangyukun + */ +@Service +public class ServiceProxy { + + private final Map serviceHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new ServiceProxy with the given ServiceInnerHandler and ConsoleConfig. The handler is mapped to a + * deployment type key. + * + * @param serviceInnerHandler the default implementation of ServiceHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + public ServiceProxy(ServiceInnerHandler serviceInnerHandler, ConsoleConfig consoleConfig) { + this.serviceHandlerMap.put("merged", serviceInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Creates a new service by delegating the operation to the appropriate handler. + * + * @param serviceForm the service form containing the service details + * @throws Exception if an error occurs during service creation + */ + public void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + serviceHandler.createService(serviceForm, serviceMetadata); + } + + /** + * Deletes an existing service by delegating the operation to the appropriate handler. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @throws Exception if an error occurs during service deletion + */ + public void deleteService(String namespaceId, String serviceName, String groupName) throws Exception { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + serviceHandler.deleteService(namespaceId, serviceName, groupName); + } + + /** + * Updates an existing service by delegating the operation to the appropriate handler. + * + * @param serviceForm the service form containing the service details + * @param service the service object created from serviceForm + * @param serviceMetadata the service metadata created from serviceForm + * @throws Exception if an error occurs during service update + */ + public void updateService(ServiceForm serviceForm, com.alibaba.nacos.naming.core.v2.pojo.Service service, + ServiceMetadata serviceMetadata, Map metadata) throws Exception { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + serviceHandler.updateService(serviceForm, service, serviceMetadata, metadata); + } + + /** + * Retrieves all selector types by delegating the operation to the appropriate handler. + * + * @return a list of selector types + */ + public List getSelectorTypeList() { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return serviceHandler.getSelectorTypeList(); + } + + /** + * Retrieves the list of subscribers for a service by delegating the operation to the appropriate handler. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param aggregation whether to aggregate the results + * @return a JSON node containing the list of subscribers + * @throws Exception if an error occurs during fetching subscribers + */ + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + boolean aggregation) throws Exception { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation); + } + + /** + * Retrieves the list of services and their details by delegating the operation to the appropriate handler. + * + * @param withInstances whether to include instances + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the size of the page + * @param serviceName the service name + * @param groupName the group name + * @param containedInstance instance name pattern which will be contained in detail + * @param hasIpCount whether to filter services with empty instances + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + public Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, + String serviceName, String groupName, String containedInstance, boolean hasIpCount) throws NacosException { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return serviceHandler.getServiceList(withInstances, namespaceId, pageNo, pageSize, serviceName, groupName, + containedInstance, hasIpCount); + } + + /** + * Retrieves the details of a specific service by delegating the operation to the appropriate handler. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) + throws NacosException { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return serviceHandler.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName); + } +} + 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 03/17] [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); + } +} + From 2ab42c0e13116b6e9d6c8ca711aab1930498aa9f 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:36:10 +0800 Subject: [PATCH 04/17] [ISSUE #12017] Add the console backend API for core section * Add cluster handling module --- .../v3/core/ConsoleClusterController.java | 112 ++++++++++++++++++ .../console/handler/core/ClusterHandler.java | 51 ++++++++ .../inner/core/ClusterInnerHandler.java | 94 +++++++++++++++ .../console/proxy/core/ClusterProxy.java | 88 ++++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java new file mode 100644 index 00000000000..952a9e8662b --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java @@ -0,0 +1,112 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.CommonParams; +import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker; +import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.ConvertUtils; +import com.alibaba.nacos.common.utils.NumberUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.core.ClusterProxy; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.WebUtils; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.web.bind.annotation.GetMapping; +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 java.util.Collection; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/console/core/cluster") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleClusterController { + + private final ClusterProxy clusterProxy; + + /** + * Constructs a new ConsoleClusterController with the provided ClusterProxy. + * + * @param clusterProxy the proxy used for handling cluster-related operations + */ + public ConsoleClusterController(ClusterProxy clusterProxy) { + this.clusterProxy = clusterProxy; + } + + /** + * The console displays the list of cluster members. + * + * @param ipKeyWord search keyWord + * @return all members + */ + @GetMapping(value = "/nodes") + @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) + public Result> getNodeList(@RequestParam(value = "keyword", required = false) String ipKeyWord) { + Collection result = clusterProxy.getNodeList(ipKeyWord); + return Result.success(result); + } + + + /** + * Update cluster. + * + * @param request http request + * @return 'ok' if success + * @throws Exception if failed + */ + @PutMapping + @Secured(action = ActionTypes.WRITE) + public Result updateCluster(HttpServletRequest request) throws Exception { + final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, + Constants.DEFAULT_NAMESPACE_ID); + final String clusterName = WebUtils.required(request, CommonParams.CLUSTER_NAME); + final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); + + ClusterMetadata clusterMetadata = new ClusterMetadata(); + clusterMetadata.setHealthyCheckPort(NumberUtils.toInt(WebUtils.required(request, "checkPort"))); + clusterMetadata.setUseInstancePortForCheck( + ConvertUtils.toBoolean(WebUtils.required(request, "useInstancePort4Check"))); + AbstractHealthChecker healthChecker = HealthCheckerFactory.deserialize( + WebUtils.required(request, "healthChecker")); + clusterMetadata.setHealthChecker(healthChecker); + clusterMetadata.setHealthyCheckType(healthChecker.getType()); + clusterMetadata.setExtendData( + UtilsAndCommons.parseMetadata(WebUtils.optional(request, "metadata", StringUtils.EMPTY))); + + clusterProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + return Result.success("ok"); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java new file mode 100644 index 00000000000..608e89e03df --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java @@ -0,0 +1,51 @@ +/* + * 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.core; + +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; + +import java.util.Collection; + +/** + * Interface for handling cluster-related operations. + * + * @author zhangyukun + */ +public interface ClusterHandler { + + /** + * Retrieve a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + */ + Collection getNodeList(String ipKeyWord); + + /** + * Update the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + */ + void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception; +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java new file mode 100644 index 00000000000..6500cfd2531 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java @@ -0,0 +1,94 @@ +/* + * 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.core; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.handler.core.ClusterHandler; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.naming.core.ClusterOperatorV2Impl; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Implementation of ClusterHandler that handles cluster-related operations. + * + * @author zhangyukun + */ +@Service +public class ClusterInnerHandler implements ClusterHandler { + + private final ServerMemberManager memberManager; + + private final ClusterOperatorV2Impl clusterOperatorV2; + + /** + * Constructs a new ClusterInnerHandler with the provided dependencies. + * + * @param memberManager the manager for server members + * @param clusterOperatorV2 the operator for cluster operations + */ + @Autowired + public ClusterInnerHandler(ServerMemberManager memberManager, ClusterOperatorV2Impl clusterOperatorV2) { + this.memberManager = memberManager; + this.clusterOperatorV2 = clusterOperatorV2; + } + + /** + * Retrieves a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + */ + @Override + public Collection getNodeList(String ipKeyWord) { + Collection members = memberManager.allMembers(); + Collection result = new ArrayList<>(); + + members.stream().sorted().forEach(member -> { + if (StringUtils.isBlank(ipKeyWord)) { + result.add(member); + return; + } + final String address = member.getAddress(); + if (StringUtils.equals(address, ipKeyWord) || StringUtils.startsWith(address, ipKeyWord)) { + result.add(member); + } + }); + + return result; + } + + /** + * Updates the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + */ + @Override + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception { + clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java new file mode 100644 index 00000000000..a2317d04c5f --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java @@ -0,0 +1,88 @@ +/* + * 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.core; + +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.core.ClusterHandler; +import com.alibaba.nacos.console.handler.inner.core.ClusterInnerHandler; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Proxy class for handling cluster-related operations. + * + * @author zhangyukun + */ +@Service +public class ClusterProxy { + + private final Map clusterHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + /** + * Constructs a new ClusterProxy with the given ClusterInnerHandler and ConsoleConfig. + * + * @param clusterInnerHandler the default implementation of ClusterHandler + * @param consoleConfig the console configuration used to determine the deployment type + */ + public ClusterProxy(ClusterInnerHandler clusterInnerHandler, ConsoleConfig consoleConfig) { + this.clusterHandlerMap.put("merged", clusterInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Retrieve a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + * @throws IllegalArgumentException if the deployment type is invalid + */ + public Collection getNodeList(String ipKeyWord) { + ClusterHandler clusterHandler = clusterHandlerMap.get(consoleConfig.getType()); + if (clusterHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + return clusterHandler.getNodeList(ipKeyWord); + } + + /** + * Updates the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + * @throws IllegalArgumentException if the deployment type is invalid + */ + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { + ClusterHandler clusterHandler = clusterHandlerMap.get(consoleConfig.getType()); + if (clusterHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + clusterHandler.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } +} + From e3a35c19e02df775eb1aabc9401a952f930fa639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Wed, 28 Aug 2024 13:24:08 +0800 Subject: [PATCH 05/17] [ISSUE #12017] Fix the error by adding ApiType * Fix the error by adding ApiType --- .../com/alibaba/nacos/auth/enums/ApiType.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/enums/ApiType.java diff --git a/auth/src/main/java/com/alibaba/nacos/auth/enums/ApiType.java b/auth/src/main/java/com/alibaba/nacos/auth/enums/ApiType.java new file mode 100644 index 00000000000..090fdc74b40 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/enums/ApiType.java @@ -0,0 +1,45 @@ +/* + * 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.auth.enums; + +/** + * The type of API. + * + * @author zhangyukun + */ +public enum ApiType { + /** + * console API. + */ + CONSOLE_API("CONSOLE_API"), + /** + * server API. + */ + OPEN_API("OPEN_API"); + + private final String description; + + ApiType(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } +} \ No newline at end of file From bc9126449d49b10c09047e7e975837f44c6705bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Fri, 30 Aug 2024 21:39:20 +0800 Subject: [PATCH 06/17] [ISSUE #12017] Refactor the old version of the console's controller * Refactor the old version of the console's controller --- .../v3/ConsoleHealthController.java | 69 +++++++ .../v3/ConsoleServerStateController.java | 81 ++++++++ .../v3/core/ConsoleNamespaceController.java | 185 ++++++++++++++++++ .../nacos/console/handler/HealthHandler.java | 36 ++++ .../console/handler/ServerStateHandler.java | 51 +++++ .../handler/core/NamespaceHandler.java | 85 ++++++++ .../handler/inner/HealthInnerHandler.java | 43 ++++ .../inner/ServerStateInnerHandler.java | 79 ++++++++ .../inner/core/NamespaceInnerHandler.java | 80 ++++++++ .../nacos/console/proxy/HealthProxy.java | 60 ++++++ .../nacos/console/proxy/ServerStateProxy.java | 68 +++++++ .../console/proxy/core/NamespaceProxy.java | 115 +++++++++++ 12 files changed, 952 insertions(+) create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/HealthInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/ServerStateInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/core/NamespaceInnerHandler.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java create mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java new file mode 100644 index 00000000000..17a461b0f68 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java @@ -0,0 +1,69 @@ +/* + * 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; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.HealthProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * Controller class for handling health check operations. + * + * @author zhangyukun on:2024/8/27 + */ +@RestController() +@RequestMapping("/v3/console/health") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleHealthController { + + private final HealthProxy healthProxy; + + public ConsoleHealthController(HealthProxy healthProxy) { + this.healthProxy = healthProxy; + } + + /** + * Whether the Nacos is in broken states or not, and cannot recover except by being restarted. + * + * @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that + * Nacos is in broken states. + */ + @GetMapping("/liveness") + public Result liveness() { + return Result.success("ok"); + } + + /** + * Ready to receive the request or not. + * + * @return HTTP code equal to 200 indicates that Nacos is ready. HTTP code equal to 500 indicates that Nacos is not + * ready. + */ + @GetMapping("/readiness") + public Result readiness(HttpServletRequest request) throws NacosException { + return healthProxy.checkReadiness(); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java new file mode 100644 index 00000000000..50f9746e42d --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java @@ -0,0 +1,81 @@ +/* + * 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; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.ServerStateProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * Controller for managing server state-related operations. + * + * @author zhangyukun on:2024/8/27 + */ +@RestController +@RequestMapping("/v3/console/server") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleServerStateController { + + private final ServerStateProxy serverStateProxy; + + public ConsoleServerStateController(ServerStateProxy serverStateProxy) { + this.serverStateProxy = serverStateProxy; + } + + /** + * Get server state of current server. + * + * @return state json. + */ + @GetMapping("/state") + public Result> serverState() { + Map serverState = serverStateProxy.getServerState(); + return Result.success(serverState); + } + + /** + * Get the announcement content based on the specified language. + * + * @param language Language for the announcement (default: "zh-CN") + * @return Announcement content as a string wrapped in a Result object + */ + @GetMapping("/announcement") + public Result getAnnouncement( + @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { + String announcement = serverStateProxy.getAnnouncement(language); + return Result.success(announcement); + } + + /** + * Get the console UI guide information. + * + * @return Console UI guide information as a string wrapped in a Result object + */ + @GetMapping("/guide") + public Result getConsoleUiGuide() { + String guideInformation = serverStateProxy.getConsoleUiGuide(); + return Result.success(guideInformation); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java new file mode 100644 index 00000000000..86da70d8678 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java @@ -0,0 +1,185 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +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.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.core.NamespaceProxy; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import org.springframework.http.HttpStatus; +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 java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * Controller for handling HTTP requests related to namespace operations. + * + * @author zhangyukun on:2024/8/27 + */ +@RestController +@RequestMapping("/v3/console/namespace") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleNamespaceController { + + private final NamespaceProxy namespaceProxy; + + private final NamespacePersistService namespacePersistService; + + public ConsoleNamespaceController(NamespaceProxy namespaceProxy, NamespacePersistService namespacePersistService) { + this.namespaceProxy = namespaceProxy; + this.namespacePersistService = namespacePersistService; + } + + private final Pattern namespaceIdCheckPattern = Pattern.compile("^[\\w-]+"); + + private final Pattern namespaceNameCheckPattern = Pattern.compile("^[^@#$%^&*]+$"); + + private static final int NAMESPACE_ID_MAX_LENGTH = 128; + + /** + * Get namespace list. + * + * @return namespace list + */ + @GetMapping("/list") + public Result> getNamespaceList() throws NacosException { + return Result.success(namespaceProxy.getNamespaceList()); + } + + /** + * get namespace all info by namespace id. + * + * @param namespaceId namespaceId + * @return namespace all info + */ + @GetMapping() + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE) + public Result getNamespaceDetail(@RequestParam("namespaceId") String namespaceId) throws NacosException { + return Result.success(namespaceProxy.getNamespaceDetail(namespaceId)); + } + + /** + * create namespace. + * + * @param namespaceForm namespaceForm. + * @return whether create ok + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + public Result createNamespace(NamespaceForm namespaceForm) throws NacosException { + + namespaceForm.validate(); + + String namespaceId = namespaceForm.getNamespaceId(); + String namespaceName = namespaceForm.getNamespaceName(); + String namespaceDesc = namespaceForm.getNamespaceDesc(); + + if (StringUtils.isBlank(namespaceId)) { + namespaceId = UUID.randomUUID().toString(); + } else { + namespaceId = namespaceId.trim(); + if (!namespaceIdCheckPattern.matcher(namespaceId).matches()) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "namespaceId [" + namespaceId + "] mismatch the pattern"); + } + if (namespaceId.length() > NAMESPACE_ID_MAX_LENGTH) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "too long namespaceId, over " + NAMESPACE_ID_MAX_LENGTH); + } + // check unique + if (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "the namespaceId is existed, namespaceId: " + namespaceForm.getNamespaceId()); + } + } + // contains illegal chars + if (!namespaceNameCheckPattern.matcher(namespaceName).matches()) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "namespaceName [" + namespaceName + "] contains illegal char"); + } + return Result.success(namespaceProxy.createNamespace(namespaceId, namespaceName, namespaceDesc)); + } + + /** + * edit namespace. + * + * @param namespaceForm namespace params + * @return whether edit ok + */ + @PutMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + public Result updateNamespace(NamespaceForm namespaceForm) throws NacosException { + namespaceForm.validate(); + // contains illegal chars + if (!namespaceNameCheckPattern.matcher(namespaceForm.getNamespaceName()).matches()) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "namespaceName [" + namespaceForm.getNamespaceName() + "] contains illegal char"); + } + return Result.success(namespaceProxy.updateNamespace(namespaceForm)); + } + + /** + * delete namespace by id. + * + * @param namespaceId namespace ID + * @return whether delete ok + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + public Result deleteNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { + return Result.success(namespaceProxy.deleteNamespace(namespaceId)); + } + + /** + * check namespaceId exist. + * + * @param namespaceId namespace id + * @return true if exist, otherwise false + */ + @GetMapping("/exist") + public Result checkNamespaceIdExist(@RequestParam("customNamespaceId") String namespaceId) + throws NacosException { + if (StringUtils.isBlank(namespaceId)) { + return Result.success(false); + } + return Result.success(namespaceProxy.checkNamespaceIdExist(namespaceId)); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java new file mode 100644 index 00000000000..9866cf330ee --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java @@ -0,0 +1,36 @@ +/* + * 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; + +import com.alibaba.nacos.api.model.v2.Result; + +/** + * Interface for handling health check operations. + * + * @author zhangyukun + */ +public interface HealthHandler { + + /** + * Perform readiness check to determine if Nacos is ready to handle requests. + * + * @return readiness result + */ + Result checkReadiness(); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java new file mode 100644 index 00000000000..4382db996e1 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java @@ -0,0 +1,51 @@ +/* + * 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; + +import java.util.Map; + +/** + * Interface for handling server state operations. + * + * @author zhangyukun + */ +public interface ServerStateHandler { + + /** + * Get the current state of the server. + * + * @return a map containing the server state + */ + Map getServerState(); + + /** + * Get the announcement content based on the language. + * + * @param language the language for the announcement + * @return the announcement content + */ + String getAnnouncement(String language); + + /** + * Get the console UI guide information. + * + * @return the console UI guide information + */ + String getConsoleUiGuide(); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java new file mode 100644 index 00000000000..63ac5f75663 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java @@ -0,0 +1,85 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; + +import java.util.List; + +/** + * Interface for handling namespace-related operations. + * + * @author zhangyukun + */ +public interface NamespaceHandler { + + /** + * Get a list of namespaces. + * + * @return list of namespaces + */ + List getNamespaceList(); + + /** + * Get details of a specific namespace. + * + * @param namespaceId the ID of the namespace + * @return namespace details + * @throws NacosException if there is an issue fetching the namespace + */ + Namespace getNamespaceDetail(String namespaceId) throws NacosException; + + /** + * Create a new namespace. + * + * @param namespaceId the ID of the namespace + * @param namespaceName the name of the namespace + * @param namespaceDesc the description of the namespace + * @return true if the namespace was successfully created, otherwise false + * @throws NacosException if there is an issue creating the namespace + */ + Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) throws NacosException; + + /** + * Update an existing namespace. + * + * @param namespaceForm the form containing the updated namespace details + * @return true if the namespace was successfully updated, otherwise false + * @throws NacosException if there is an issue updating the namespace + */ + Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException; + + /** + * Delete a namespace by its ID. + * + * @param namespaceId the ID of the namespace + * @return true if the namespace was successfully deleted, otherwise false + */ + Boolean deleteNamespace(String namespaceId); + + /** + * Check if a namespace ID exists. + * + * @param namespaceId the ID of the namespace to check + * @return true if the namespace exists, otherwise false + */ + Boolean checkNamespaceIdExist(String namespaceId); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/HealthInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/HealthInnerHandler.java new file mode 100644 index 00000000000..82c2512c4bf --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/HealthInnerHandler.java @@ -0,0 +1,43 @@ +/* + * 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; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.handler.HealthHandler; +import com.alibaba.nacos.core.cluster.health.ModuleHealthCheckerHolder; +import com.alibaba.nacos.core.cluster.health.ReadinessResult; +import org.springframework.stereotype.Service; + +/** + * Implementation of HealthHandler that performs health check operations. + * + * @author zhangyukun + */ +@Service +public class HealthInnerHandler implements HealthHandler { + + @Override + public Result checkReadiness() { + ReadinessResult result = ModuleHealthCheckerHolder.getInstance().checkReadiness(); + if (result.isSuccess()) { + return Result.success("ok"); + } + return Result.failure(result.getResultMessage()); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/ServerStateInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/ServerStateInnerHandler.java new file mode 100644 index 00000000000..e6beb1cf4b6 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/ServerStateInnerHandler.java @@ -0,0 +1,79 @@ +/* + * 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; + +import com.alibaba.nacos.console.handler.ServerStateHandler; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.module.ModuleState; +import com.alibaba.nacos.sys.module.ModuleStateHolder; +import com.alibaba.nacos.sys.utils.DiskUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.common.utils.StringUtils.FOLDER_SEPARATOR; +import static com.alibaba.nacos.common.utils.StringUtils.TOP_PATH; +import static com.alibaba.nacos.common.utils.StringUtils.WINDOWS_FOLDER_SEPARATOR; + +/** + * Implementation of ServerStateHandler that performs server state operations. + * + * @author zhangyukun + */ +@Service +public class ServerStateInnerHandler implements ServerStateHandler { + + private static final String ANNOUNCEMENT_FILE = "announcement.conf"; + + private static final String GUIDE_FILE = "console-guide.conf"; + + public Map getServerState() { + Map serverState = new HashMap<>(4); + for (ModuleState each : ModuleStateHolder.getInstance().getAllModuleStates()) { + each.getStates().forEach((s, o) -> serverState.put(s, null == o ? null : o.toString())); + } + return serverState; + } + + @Override + public String getAnnouncement(String language) { + String file = ANNOUNCEMENT_FILE.substring(0, ANNOUNCEMENT_FILE.length() - 5) + "_" + language + ".conf"; + if (file.contains(TOP_PATH) || file.contains(FOLDER_SEPARATOR) || file.contains(WINDOWS_FOLDER_SEPARATOR)) { + throw new IllegalArgumentException("Invalid filename"); + } + File announcementFile = new File(EnvUtil.getConfPath(), file); + String announcement = null; + if (announcementFile.exists() && announcementFile.isFile()) { + announcement = DiskUtils.readFile(announcementFile); + } + return announcement; + } + + @Override + public String getConsoleUiGuide() { + File guideFile = new File(EnvUtil.getConfPath(), GUIDE_FILE); + String guideInformation = null; + if (guideFile.exists() && guideFile.isFile()) { + guideInformation = DiskUtils.readFile(guideFile); + } + return guideInformation; + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/NamespaceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/NamespaceInnerHandler.java new file mode 100644 index 00000000000..6d121329794 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/NamespaceInnerHandler.java @@ -0,0 +1,80 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.handler.core.NamespaceHandler; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.core.service.NamespaceOperationService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Implementation of NamespaceHandler that handles namespace-related operations. + * + * @author zhangyukun + */ +@Service +public class NamespaceInnerHandler implements NamespaceHandler { + + private final NamespaceOperationService namespaceOperationService; + + private final NamespacePersistService namespacePersistService; + + public NamespaceInnerHandler(NamespaceOperationService namespaceOperationService, + NamespacePersistService namespacePersistService) { + this.namespaceOperationService = namespaceOperationService; + this.namespacePersistService = namespacePersistService; + } + + @Override + public List getNamespaceList() { + return namespaceOperationService.getNamespaceList(); + } + + @Override + public Namespace getNamespaceDetail(String namespaceId) throws NacosException { + return namespaceOperationService.getNamespace(namespaceId); + } + + @Override + public Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) + throws NacosException { + return namespaceOperationService.createNamespace(namespaceId, namespaceName, namespaceDesc); + } + + @Override + public Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException { + return namespaceOperationService.editNamespace(namespaceForm.getNamespaceId(), namespaceForm.getNamespaceName(), + namespaceForm.getNamespaceDesc()); + } + + @Override + public Boolean deleteNamespace(String namespaceId) { + return namespaceOperationService.removeNamespace(namespaceId); + } + + @Override + public Boolean checkNamespaceIdExist(String namespaceId) { + return (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java new file mode 100644 index 00000000000..21a1a13d3ad --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java @@ -0,0 +1,60 @@ +/* + * 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; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.HealthHandler; +import com.alibaba.nacos.console.handler.inner.HealthInnerHandler; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * Proxy class for handling health check operations. + * + * @author zhangyukun + */ +@Service +public class HealthProxy { + + private final Map healthHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + public HealthProxy(HealthInnerHandler healthInnerHandler, ConsoleConfig consoleConfig) { + this.healthHandlerMap.put("merged", healthInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Perform readiness check to determine if Nacos is ready to handle requests. + * + * @return readiness result + */ + public Result checkReadiness() throws NacosException { + HealthHandler healthHandler = healthHandlerMap.get(consoleConfig.getType()); + if (healthHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return healthHandler.checkReadiness(); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java new file mode 100644 index 00000000000..29cbc751b86 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java @@ -0,0 +1,68 @@ +/* + * 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; + +import com.alibaba.nacos.console.handler.ServerStateHandler; +import com.alibaba.nacos.console.handler.inner.ServerStateInnerHandler; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * Proxy class for handling server state operations. + * + * @author zhangyukun + */ +@Service +public class ServerStateProxy { + + private final ServerStateHandler serverStateHandler; + + public ServerStateProxy(ServerStateInnerHandler serverStateHandler) { + this.serverStateHandler = serverStateHandler; + } + + /** + * Get the current state of the server. + * + * @return the server state as a Map + */ + public Map getServerState() { + return serverStateHandler.getServerState(); + } + + /** + * Get the announcement content based on the language. + * + * @param language the language for the announcement + * @return the announcement content as a String + */ + public String getAnnouncement(String language) { + return serverStateHandler.getAnnouncement(language); + } + + /** + * Get the console UI guide information. + * + * @return the console UI guide information as a String + */ + public String getConsoleUiGuide() { + return serverStateHandler.getConsoleUiGuide(); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java new file mode 100644 index 00000000000..b3d47a7e0d8 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java @@ -0,0 +1,115 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.core.NamespaceHandler; +import com.alibaba.nacos.console.handler.inner.core.NamespaceInnerHandler; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Proxy class for handling namespace operations. + * + * @author zhangyukun + */ +@Service +public class NamespaceProxy { + + private final Map namespaceHandlerMap = new HashMap<>(); + + private final ConsoleConfig consoleConfig; + + public NamespaceProxy(NamespaceInnerHandler namespaceInnerHandler, ConsoleConfig consoleConfig) { + this.namespaceHandlerMap.put("merged", namespaceInnerHandler); + this.consoleConfig = consoleConfig; + } + + /** + * Get namespace list. + */ + public List getNamespaceList() throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.getNamespaceList(); + } + + /** + * Get the specific namespace information. + */ + public Namespace getNamespaceDetail(String namespaceId) throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.getNamespaceDetail(namespaceId); + } + + /** + * Create or update namespace. + */ + public Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) + throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.createNamespace(namespaceId, namespaceName, namespaceDesc); + } + + /** + * Edit namespace. + */ + public Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.updateNamespace(namespaceForm); + } + + /** + * Delete namespace. + */ + public Boolean deleteNamespace(String namespaceId) throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.deleteNamespace(namespaceId); + } + + /** + * Check if namespace exists. + */ + public Boolean checkNamespaceIdExist(String namespaceId) throws NacosException { + NamespaceHandler namespaceHandler = namespaceHandlerMap.get(consoleConfig.getType()); + if (namespaceHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return namespaceHandler.checkNamespaceIdExist(namespaceId); + } +} From 4824ab0896bcd82210ea7f03740910000e346562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 3 Sep 2024 21:56:39 +0800 Subject: [PATCH 07/17] Revert "[ISSUE #12017] Add the console backend API for auth section" This reverts commit 052112a709734eaad3857d4a04831db9ae291b34. --- .../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 deletions(-) delete mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java delete mode 100644 console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java delete 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 deleted file mode 100644 index 461fd46ba9d..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsolePermissionController.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 deleted file mode 100644 index 9df1fe22d25..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleRoleController.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 deleted file mode 100644 index ce14818ef0e..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/auth/ConsoleUserController.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 deleted file mode 100644 index b14f8040061..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/auth/PermissionHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 deleted file mode 100644 index c7219718e40..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/auth/RoleHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 deleted file mode 100644 index ba61ad7a2e7..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/auth/UserHandler.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 deleted file mode 100644 index 091006f03dd..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/PermissionInnerHandler.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 deleted file mode 100644 index 283d5976dc0..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/RoleInnerHandler.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 deleted file mode 100644 index 242be922f27..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/auth/UserInnerHandler.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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 deleted file mode 100644 index 998f34685df..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/PermissionProxy.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 deleted file mode 100644 index f9d7401b060..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/RoleProxy.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 deleted file mode 100644 index 77de99a0555..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/auth/UserProxy.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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); - } -} - From 363048c3dc4b7d0236e1e8dd4e344ccdc1488ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Wed, 4 Sep 2024 13:23:05 +0800 Subject: [PATCH 08/17] [ISSUE #12017] Updated ControllerV3 for auth section * Updated ControllerV3 for auth section --- .../controller/v3/PermissionControllerV3.java | 116 ++++++ .../impl/controller/v3/RoleControllerV3.java | 129 +++++++ .../impl/controller/v3/UserControllerV3.java | 329 ++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java new file mode 100644 index 00000000000..aa85bc23ede --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java @@ -0,0 +1,116 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +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.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 com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +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/auth/permission") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class PermissionControllerV3 { + + private final NacosRoleServiceImpl nacosRoleService; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + /** + * Constructs a new ConsolePermissionController with the provided PermissionProxy. + * + * @param nacosRoleService nacosRoleService instance + */ + @Autowired + public PermissionControllerV3(NacosRoleServiceImpl nacosRoleService) { + this.nacosRoleService = nacosRoleService; + } + + /** + * 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) { + nacosRoleService.addPermission(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) { + nacosRoleService.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; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + permissionPage = nacosRoleService.findPermissionsLike4Page(role, pageNo, pageSize); + } else { + permissionPage = nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize); + } + return Result.success(permissionPage); + } +} \ No newline at end of file diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java new file mode 100644 index 00000000000..79136866679 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java @@ -0,0 +1,129 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +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.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 com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +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; + +/** + * Controller for handling HTTP requests related to role operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/auth/role") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class RoleControllerV3 { + + private final NacosRoleServiceImpl roleService; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + public RoleControllerV3(NacosRoleServiceImpl roleService) { + this.roleService = roleService; + } + + /** + * 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) { + roleService.addRole(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) { + if (StringUtils.isBlank(username)) { + roleService.deleteRole(role); + } else { + roleService.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; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + rolePage = roleService.findRolesLike4Page(username, role, pageNo, pageSize); + } else { + rolePage = roleService.getRolesFromDatabase(username, role, pageNo, pageSize); + } + 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 = roleService.findRolesLikeRoleName(role); + return Result.success(roles); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java new file mode 100644 index 00000000000..7dbf06cfcb1 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java @@ -0,0 +1,329 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +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.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +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.alibaba.nacos.plugin.auth.impl.utils.PasswordGeneratorUtil; +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.web.HttpSessionRequiredException; +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; + +/** + * Controller for handling HTTP requests related to user operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/auth/user") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class UserControllerV3 { + + private final NacosUserDetailsServiceImpl userDetailsService; + + private final NacosRoleServiceImpl roleService; + + private final AuthConfigs authConfigs; + + private final IAuthenticationManager iAuthenticationManager; + + private final TokenManagerDelegate jwtTokenManager; + + private final AuthenticationManager authenticationManager; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + /** + * 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 UserControllerV3(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; + } + + /** + * 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) { + User user = userDetailsService.getUserFromDatabase(username); + if (user != null) { + throw new IllegalArgumentException("user '" + username + "' already exist!"); + } + userDetailsService.createUser(username, PasswordEncoderUtil.encode(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(); + } + + 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"); + } + } + + /** + * 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) { + 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 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 { + 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 Result.success("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); + } + + + /** + * 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; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + userPage = userDetailsService.findUsersLike4Page(username, pageNo, pageSize); + } else { + userPage = userDetailsService.getUsersFromDatabase(pageNo, pageSize, username); + } + 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 = userDetailsService.findUserLikeUsername(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 { + 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"); + } + } +} + From 215ab4be9657b35ff48e98592894752f28752928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Wed, 4 Sep 2024 13:31:06 +0800 Subject: [PATCH 09/17] [ISSUE #12017] Fix errors * Add ApiType annotation * Transfer the updateCluster method * Fix Compilation Errors --- .../v3/config/ConsoleHistoryController.java | 11 +++-- .../v3/core/ConsoleClusterController.java | 45 +----------------- .../v3/naming/ConsoleInstanceController.java | 5 +- .../v3/naming/ConsoleServiceController.java | 47 ++++++++++++++++--- .../console/handler/core/ClusterHandler.java | 12 ----- .../inner/core/ClusterInnerHandler.java | 24 +--------- .../inner/naming/ServiceInnerHandler.java | 12 ++++- .../handler/naming/ServiceHandler.java | 15 ++++++ .../console/proxy/core/ClusterProxy.java | 20 -------- .../console/proxy/naming/ServiceProxy.java | 21 +++++++++ 10 files changed, 100 insertions(+), 112 deletions(-) diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java index 4e23dc72532..1efbe09ab62 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java @@ -20,6 +20,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; @@ -40,7 +41,7 @@ import java.util.List; /** - * . + * Controller for handling HTTP requests related to history operations. * * @author zhangyukun on:2024/8/16 */ @@ -66,7 +67,7 @@ public ConsoleHistoryController(HistoryProxy historyProxy) { * @return history config info */ @GetMapping - @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) public Result getConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -91,7 +92,7 @@ public Result getConfigHistoryInfo(@RequestParam("dataId") St * @return the page of history config. */ @GetMapping("/list") - @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) public Result> listConfigHistory(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -118,7 +119,7 @@ public Result> listConfigHistory(@RequestParam("dataId") * @return history config info */ @GetMapping(value = "/previous") - @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) public Result getPreviousConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -139,7 +140,7 @@ public Result getPreviousConfigHistoryInfo(@RequestParam("dat * @return list */ @GetMapping(value = "/configs") - @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) public Result> getConfigsByTenant(@RequestParam("namespaceId") String namespaceId) throws NacosException { // check namespaceId diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java index 952a9e8662b..6f5f6b6f85a 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java @@ -17,36 +17,24 @@ package com.alibaba.nacos.console.controller.v3.core; -import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.model.v2.Result; -import com.alibaba.nacos.api.naming.CommonParams; -import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker; -import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.common.utils.ConvertUtils; -import com.alibaba.nacos.common.utils.NumberUtils; -import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.core.ClusterProxy; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.Commons; -import com.alibaba.nacos.core.utils.WebUtils; -import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; -import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.web.bind.annotation.GetMapping; -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 java.util.Collection; /** - * . + * Controller for handling HTTP requests related to cluster operations. * * @author zhangyukun on:2024/8/16 */ @@ -78,35 +66,4 @@ public Result> getNodeList(@RequestParam(value = "keyword", r Collection result = clusterProxy.getNodeList(ipKeyWord); return Result.success(result); } - - - /** - * Update cluster. - * - * @param request http request - * @return 'ok' if success - * @throws Exception if failed - */ - @PutMapping - @Secured(action = ActionTypes.WRITE) - public Result updateCluster(HttpServletRequest request) throws Exception { - final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, - Constants.DEFAULT_NAMESPACE_ID); - final String clusterName = WebUtils.required(request, CommonParams.CLUSTER_NAME); - final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); - - ClusterMetadata clusterMetadata = new ClusterMetadata(); - clusterMetadata.setHealthyCheckPort(NumberUtils.toInt(WebUtils.required(request, "checkPort"))); - clusterMetadata.setUseInstancePortForCheck( - ConvertUtils.toBoolean(WebUtils.required(request, "useInstancePort4Check"))); - AbstractHealthChecker healthChecker = HealthCheckerFactory.deserialize( - WebUtils.required(request, "healthChecker")); - clusterMetadata.setHealthChecker(healthChecker); - clusterMetadata.setHealthyCheckType(healthChecker.getType()); - clusterMetadata.setExtendData( - UtilsAndCommons.parseMetadata(WebUtils.optional(request, "metadata", StringUtils.EMPTY))); - - clusterProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); - return Result.success("ok"); - } } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java index ed1a376cf78..6402641ec9a 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java @@ -26,6 +26,7 @@ import com.alibaba.nacos.api.naming.pojo.builder.InstanceBuilder; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.naming.InstanceProxy; import com.alibaba.nacos.core.control.TpsControl; @@ -77,7 +78,7 @@ public ConsoleInstanceController(InstanceProxy instanceProxy, SwitchDomain switc * @param pageSize size of each page * @return instances information */ - @Secured(action = ActionTypes.READ) + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) @RequestMapping("/list") public Result getInstanceList( @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @@ -96,7 +97,7 @@ public Result getInstanceList( @CanDistro @PutMapping @TpsControl(pointName = "NamingInstanceUpdate", name = "HttpNamingInstanceUpdate") - @Secured(action = ActionTypes.WRITE) + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) public Result updateInstance(InstanceForm instanceForm) throws NacosException { // check param instanceForm.validate(); diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java index 15e2000f016..7cc17bce54d 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -23,9 +23,13 @@ import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.api.naming.CommonParams; +import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker; +import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.Selector; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; +import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.NumberUtils; import com.alibaba.nacos.common.utils.StringUtils; @@ -34,6 +38,7 @@ import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.misc.UtilsAndCommons; @@ -82,7 +87,7 @@ public ConsoleServiceController(ServiceProxy serviceProxy, SelectorManager selec */ @PostMapping() @TpsControl(pointName = "NamingServiceRegister", name = "HttpNamingServiceRegister") - @Secured(action = ActionTypes.WRITE) + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) public Result createService(ServiceForm serviceForm) throws Exception { serviceForm.validate(); ServiceMetadata serviceMetadata = new ServiceMetadata(); @@ -100,7 +105,7 @@ public Result createService(ServiceForm serviceForm) throws Exception { */ @DeleteMapping() @TpsControl(pointName = "NamingServiceDeregister", name = "HttpNamingServiceDeregister") - @Secured(action = ActionTypes.WRITE) + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) public Result deleteService( @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam("serviceName") String serviceName, @@ -115,7 +120,7 @@ public Result deleteService( */ @PutMapping() @TpsControl(pointName = "NamingServiceUpdate", name = "HttpNamingServiceUpdate") - @Secured(action = ActionTypes.WRITE) + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) public Result updateService(ServiceForm serviceForm) throws Exception { serviceForm.validate(); @@ -148,7 +153,7 @@ public Result> getSelectorTypeList() { * @return Jackson object node */ @GetMapping("/subscribers") - @Secured(action = ActionTypes.READ) + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) public Result subscribers(HttpServletRequest request) throws Exception { int pageNo = NumberUtils.toInt(WebUtils.optional(request, "pageNo", "1")); @@ -196,7 +201,7 @@ private Selector parseSelector(String selectorJsonString) throws Exception { * @param hasIpCount whether filter services with empty instance * @return list service detail */ - @Secured(action = ActionTypes.READ) + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) @GetMapping("/list") public Object getServiceList(@RequestParam(required = false) boolean withInstances, @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @@ -218,7 +223,7 @@ public Object getServiceList(@RequestParam(required = false) boolean withInstanc * @return service detail information * @throws NacosException nacos exception */ - @Secured(action = ActionTypes.READ) + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) @GetMapping() public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, String serviceName) throws NacosException { @@ -227,4 +232,34 @@ public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NA return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName)); } + /** + * Update cluster. + * + * @param request http request + * @return 'ok' if success + * @throws Exception if failed + */ + @PutMapping("/cluster") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result updateCluster(HttpServletRequest request) throws Exception { + final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, + Constants.DEFAULT_NAMESPACE_ID); + final String clusterName = WebUtils.required(request, CommonParams.CLUSTER_NAME); + final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); + + ClusterMetadata clusterMetadata = new ClusterMetadata(); + clusterMetadata.setHealthyCheckPort(NumberUtils.toInt(WebUtils.required(request, "checkPort"))); + clusterMetadata.setUseInstancePortForCheck( + ConvertUtils.toBoolean(WebUtils.required(request, "useInstancePort4Check"))); + AbstractHealthChecker healthChecker = HealthCheckerFactory.deserialize( + WebUtils.required(request, "healthChecker")); + clusterMetadata.setHealthChecker(healthChecker); + clusterMetadata.setHealthyCheckType(healthChecker.getType()); + clusterMetadata.setExtendData( + UtilsAndCommons.parseMetadata(WebUtils.optional(request, "metadata", StringUtils.EMPTY))); + + serviceProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + return Result.success("ok"); + } + } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java index 608e89e03df..f8b795de338 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java @@ -18,7 +18,6 @@ package com.alibaba.nacos.console.handler.core; import com.alibaba.nacos.core.cluster.Member; -import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import java.util.Collection; @@ -36,16 +35,5 @@ public interface ClusterHandler { * @return a collection of matching members */ Collection getNodeList(String ipKeyWord); - - /** - * Update the metadata of a cluster. - * - * @param namespaceId the namespace ID - * @param serviceName the service name - * @param clusterName the cluster name - * @param clusterMetadata the metadata for the cluster - * @throws Exception if the update operation fails - */ - void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception; } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java index 6500cfd2531..4395039c33f 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/core/ClusterInnerHandler.java @@ -21,8 +21,6 @@ import com.alibaba.nacos.console.handler.core.ClusterHandler; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.ServerMemberManager; -import com.alibaba.nacos.naming.core.ClusterOperatorV2Impl; -import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -39,18 +37,14 @@ public class ClusterInnerHandler implements ClusterHandler { private final ServerMemberManager memberManager; - private final ClusterOperatorV2Impl clusterOperatorV2; - /** * Constructs a new ClusterInnerHandler with the provided dependencies. * - * @param memberManager the manager for server members - * @param clusterOperatorV2 the operator for cluster operations + * @param memberManager the manager for server members */ @Autowired - public ClusterInnerHandler(ServerMemberManager memberManager, ClusterOperatorV2Impl clusterOperatorV2) { + public ClusterInnerHandler(ServerMemberManager memberManager) { this.memberManager = memberManager; - this.clusterOperatorV2 = clusterOperatorV2; } /** @@ -77,18 +71,4 @@ public Collection getNodeList(String ipKeyWord) { return result; } - - /** - * Updates the metadata of a cluster. - * - * @param namespaceId the namespace ID - * @param serviceName the service name - * @param clusterName the cluster name - * @param clusterMetadata the metadata for the cluster - * @throws Exception if the update operation fails - */ - @Override - public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception { - clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); - } } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java index 8b7e6df08db..548f68f37ec 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java @@ -25,8 +25,10 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.console.handler.naming.ServiceHandler; import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; +import com.alibaba.nacos.naming.core.ClusterOperatorV2Impl; import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl; import com.alibaba.nacos.naming.core.SubscribeManager; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.model.form.ServiceForm; @@ -55,13 +57,16 @@ public class ServiceInnerHandler implements ServiceHandler { private final SubscribeManager subscribeManager; + private final ClusterOperatorV2Impl clusterOperatorV2; + @Autowired public ServiceInnerHandler(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager, - CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager) { + CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, ClusterOperatorV2Impl clusterOperatorV2) { this.serviceOperatorV2 = serviceOperatorV2; this.selectorManager = selectorManager; this.catalogServiceV2 = catalogServiceV2; this.subscribeManager = subscribeManager; + this.clusterOperatorV2 = clusterOperatorV2; } @Override @@ -144,5 +149,10 @@ public Object getServiceDetail(String namespaceId, String serviceNameWithoutGrou return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceNameWithoutGroup); } + @Override + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception { + clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } + } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java index e1cb6042464..404191f9388 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java @@ -18,6 +18,7 @@ package com.alibaba.nacos.console.handler.naming; import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.model.form.ServiceForm; @@ -37,6 +38,7 @@ public interface ServiceHandler { * Create a new service. * * @param serviceForm the service form containing the service details + * @param serviceMetadata the service metadata created from serviceForm * @throws Exception if an error occurs during service creation */ void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception; @@ -57,6 +59,7 @@ public interface ServiceHandler { * @param serviceForm the service form containing the service details * @param service the service object created from serviceForm * @param serviceMetadata the service metadata created from serviceForm + * @param metadata the service metadata * @throws Exception if an error occurs during service update */ void updateService(ServiceForm serviceForm, Service service, ServiceMetadata serviceMetadata, @@ -110,5 +113,17 @@ Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int * @throws NacosException if an error occurs during fetching service details */ Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) throws NacosException; + + /** + * Update the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + */ + void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception; } diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java index a2317d04c5f..283dca9fe0f 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java @@ -21,7 +21,6 @@ import com.alibaba.nacos.console.handler.core.ClusterHandler; import com.alibaba.nacos.console.handler.inner.core.ClusterInnerHandler; import com.alibaba.nacos.core.cluster.Member; -import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import org.springframework.stereotype.Service; import java.util.Collection; @@ -65,24 +64,5 @@ public Collection getNodeList(String ipKeyWord) { } return clusterHandler.getNodeList(ipKeyWord); } - - /** - * Updates the metadata of a cluster. - * - * @param namespaceId the namespace ID - * @param serviceName the service name - * @param clusterName the cluster name - * @param clusterMetadata the metadata for the cluster - * @throws Exception if the update operation fails - * @throws IllegalArgumentException if the deployment type is invalid - */ - public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, - ClusterMetadata clusterMetadata) throws Exception { - ClusterHandler clusterHandler = clusterHandlerMap.get(consoleConfig.getType()); - if (clusterHandler == null) { - throw new IllegalArgumentException("Invalid deployment type"); - } - clusterHandler.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); - } } diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java index 28e17c46e00..d4c0632a9dc 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -19,8 +19,10 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.console.config.ConsoleConfig; +import com.alibaba.nacos.console.handler.core.ClusterHandler; import com.alibaba.nacos.console.handler.inner.naming.ServiceInnerHandler; import com.alibaba.nacos.console.handler.naming.ServiceHandler; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.model.form.ServiceForm; @@ -176,5 +178,24 @@ public Object getServiceDetail(String namespaceId, String serviceNameWithoutGrou } return serviceHandler.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName); } + + /** + * Updates the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + * @throws IllegalArgumentException if the deployment type is invalid + */ + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { + ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); + if (serviceHandler == null) { + throw new IllegalArgumentException("Invalid deployment type"); + } + serviceHandler.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } } From 819a8e1c9c11fabf59278cf08de81e7e6ed6c2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Fri, 6 Sep 2024 18:03:51 +0800 Subject: [PATCH 10/17] [ISSUE #12017] Add unit tests for all sections * Add unit tests for all sections --- .../v3/ConsoleHealthControllerTest.java | 88 +++++++ .../v3/ConsoleServerStateControllerTest.java | 113 +++++++++ .../config/ConsoleConfigControllerTest.java | 2 +- .../config/ConsoleHistoryControllerTest.java | 170 ++++++++++++++ .../v3/core/ConsoleClusterControllerTest.java | 93 ++++++++ .../core/ConsoleNamespaceControllerTest.java | 158 +++++++++++++ .../naming/ConsoleInstanceControllerTest.java | 116 ++++++++++ .../naming/ConsoleServiceControllerTest.java | 215 +++++++++++++++++ .../v3/PermissionControllerV3Test.java | 101 ++++++++ .../controller/v3/RoleControllerV3Test.java | 123 ++++++++++ .../controller/v3/UserControllerV3Test.java | 218 ++++++++++++++++++ 11 files changed, 1396 insertions(+), 1 deletion(-) create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java create mode 100644 console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java create mode 100644 plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java new file mode 100644 index 00000000000..9a4adb9f9c9 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthControllerTest.java @@ -0,0 +1,88 @@ +/* + * 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; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.HealthProxy; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * ConsoleHealthControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleHealthControllerTest { + + @Mock + private HealthProxy healthProxy; + + @InjectMocks + private ConsoleHealthController consoleHealthController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleHealthController).build(); + } + + @Test + void testLiveness() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/health/liveness"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ok", result.getData()); + } + + @Test + void testReadiness() throws Exception { + when(healthProxy.checkReadiness()).thenReturn(Result.success("ready")); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/health/readiness"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ready", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java new file mode 100644 index 00000000000..fb5bfcc2432 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java @@ -0,0 +1,113 @@ +/* + * 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; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.ServerStateProxy; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * ConsoleServerStateControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServerStateControllerTest { + + @Mock + private ServerStateProxy serverStateProxy; + + @InjectMocks + private ConsoleServerStateController consoleServerStateController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleServerStateController).build(); + } + + @Test + void testServerState() throws Exception { + Map state = new HashMap<>(); + state.put("state", "OK"); + when(serverStateProxy.getServerState()).thenReturn(state); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/state"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals("OK", result.getData().get("state")); + } + + @Test + void testGetAnnouncement() throws Exception { + when(serverStateProxy.getAnnouncement(anyString())).thenReturn("Test Announcement"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/announcement") + .param("language", "en"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Announcement", result.getData()); + } + + @Test + void testGetConsoleUiGuide() throws Exception { + when(serverStateProxy.getConsoleUiGuide()).thenReturn("Test Guide"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/guide"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Guide", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java index 66241e459f5..b6c63bfc714 100644 --- a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java @@ -73,7 +73,7 @@ import static org.mockito.Mockito.when; /** - * . + * ConsoleConfigControllerTest. * * @author zhangyukun on:2024/8/20 */ diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java new file mode 100644 index 00000000000..41e0ed88f75 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java @@ -0,0 +1,170 @@ +/* + * 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.config; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.console.proxy.config.HistoryProxy; +import com.alibaba.nacos.persistence.model.Page; +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * ConsoleHistoryControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ConsoleHistoryControllerTest { + + @InjectMocks + private ConsoleHistoryController consoleHistoryController; + + @Mock + private HistoryProxy historyProxy; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleHistoryController).build(); + } + + @Test + void testGetConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getConfigHistoryInfo("testDataId", "testGroup", "", 1L)).thenReturn(configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history") + .param("dataId", "testDataId").param("group", "testGroup").param("nid", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testListConfigHistory() throws Exception { + Page page = new Page<>(); + page.setTotalCount(1); + page.setPageNumber(1); + page.setPagesAvailable(1); + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + page.setPageItems(Collections.singletonList(configHistoryInfo)); + + when(historyProxy.listConfigHistory("testDataId", "testGroup", "", 1, 100)).thenReturn(page); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/list") + .param("dataId", "testDataId").param("group", "testGroup").param("pageNo", "1") + .param("pageSize", "100"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + Page resultPage = result.getData(); + ConfigHistoryInfo resultConfigHistoryInfo = resultPage.getPageItems().get(0); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetPreviousConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getPreviousConfigHistoryInfo("testDataId", "testGroup", "", 1L)).thenReturn( + configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/previous") + .param("dataId", "testDataId").param("group", "testGroup").param("id", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetConfigsByTenant() throws Exception { + ConfigInfoWrapper configInfo = new ConfigInfoWrapper(); + configInfo.setDataId("testDataId"); + configInfo.setGroup("testGroup"); + List configInfoList = Collections.singletonList(configInfo); + + when(historyProxy.getConfigsByTenant("testNamespaceId")).thenReturn(configInfoList); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/configs") + .param("namespaceId", "testNamespaceId"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + List resultConfigInfoList = result.getData(); + + assertEquals(1, resultConfigInfoList.size()); + assertEquals("testDataId", resultConfigInfoList.get(0).getDataId()); + assertEquals("testGroup", resultConfigInfoList.get(0).getGroup()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java new file mode 100644 index 00000000000..e07730e45c4 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java @@ -0,0 +1,93 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.ClusterProxy; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * ConsoleClusterControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleClusterControllerTest { + + @Mock + private ClusterProxy clusterProxy; + + @InjectMocks + private ConsoleClusterController consoleClusterController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleClusterController).build(); + + MockedStatic mockedEnvUtil = mockStatic(EnvUtil.class); + mockedEnvUtil.when(() -> EnvUtil.getProperty(anyString())).thenReturn("default_value"); + } + + @Test + void testGetNodeList() throws Exception { + Member member = new Member(); + member.setIp("127.0.0.1"); + member.setPort(8848); + Collection members = Arrays.asList(member); + + when(clusterProxy.getNodeList(anyString())).thenReturn(members); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/cluster/nodes") + .param("keyword", "127.0.0.1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("127.0.0.1", result.getData().iterator().next().getIp()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java new file mode 100644 index 00000000000..cc32123520d --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java @@ -0,0 +1,158 @@ +/* + * 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.core; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.NamespaceProxy; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleNamespaceControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleNamespaceControllerTest { + + @Mock + private NamespaceProxy namespaceProxy; + + @InjectMocks + private ConsoleNamespaceController consoleNamespaceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleNamespaceController).build(); + } + + @Test + void testGetNamespaceList() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + List namespaces = Arrays.asList(namespace); + when(namespaceProxy.getNamespaceList()).thenReturn(namespaces); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace/list"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("testNamespace", result.getData().get(0).getNamespace()); + } + + @Test + void testGetNamespaceDetail() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + when(namespaceProxy.getNamespaceDetail(anyString())).thenReturn(namespace); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("testNamespace", result.getData().getNamespace()); + } + + @Test + void testUpdateNamespace() throws Exception { + NamespaceForm namespaceForm = new NamespaceForm(); + namespaceForm.setNamespaceId("testNamespace"); + namespaceForm.setNamespaceName("testNamespaceName"); + namespaceForm.setNamespaceDesc("testDesc"); + + MockHttpServletRequest request = new MockHttpServletRequest(); + + when(namespaceProxy.updateNamespace(any(NamespaceForm.class))).thenReturn(true); + + Result result = consoleNamespaceController.updateNamespace(namespaceForm); + + verify(namespaceProxy).updateNamespace(any(NamespaceForm.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData()); + } + + @Test + void testDeleteNamespace() throws Exception { + when(namespaceProxy.deleteNamespace(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + assertEquals(200, response.getStatus()); + } + + @Test + void testCheckNamespaceIdExist() throws Exception { + when(namespaceProxy.checkNamespaceIdExist(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace/exist") + .param("customNamespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertTrue(result.getData()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java new file mode 100644 index 00000000000..6063cefe7c8 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java @@ -0,0 +1,116 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.console.proxy.naming.InstanceProxy; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleInstanceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleInstanceControllerTest { + + @Mock + private InstanceProxy instanceProxy; + + @Mock + private SwitchDomain switchDomain; + + @InjectMocks + private ConsoleInstanceController consoleInstanceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleInstanceController).build(); + } + + @Test + void testGetInstanceList() throws Exception { + ObjectNode instances = JsonNodeFactory.instance.objectNode(); + when(instanceProxy.listInstances(anyString(), anyString(), anyString(), anyInt(), anyInt(), any(), + any())).thenReturn(instances); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/ns/instance/list") + .param("namespaceId", "default").param("serviceName", "testService").param("pageNo", "1") + .param("pageSize", "10"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertNotNull(result.getData()); + } + + @Test + void testUpdateInstance() throws Exception { + InstanceForm instanceForm = new InstanceForm(); + instanceForm.setServiceName("testService"); + instanceForm.setIp("127.0.0.1"); + instanceForm.setPort(8080); + instanceForm.setWeight(1.0); + + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(8080); + instance.setWeight(1.0); + + doNothing().when(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + Result result = consoleInstanceController.updateInstance(instanceForm); + + verify(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals("ok", result.getData()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java new file mode 100644 index 00000000000..61e791eb6a0 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java @@ -0,0 +1,215 @@ +/* + * 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.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.naming.ServiceProxy; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.selector.LabelSelector; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.omg.CORBA.ServiceDetail; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +/** + * ConsoleServiceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServiceControllerTest { + + @Mock + private ServiceProxy serviceProxy; + + @Mock + private SelectorManager selectorManager; + + @InjectMocks + private ConsoleServiceController consoleServiceController; + + @BeforeEach + void setUp() { + consoleServiceController = new ConsoleServiceController(serviceProxy, selectorManager); + } + + @Test + void testCreateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.createService(serviceForm); + + verify(serviceProxy).createService(eq(serviceForm), any(ServiceMetadata.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testDeleteService() throws Exception { + Result actual = consoleServiceController.deleteService("testNamespace", "testService", "testGroup"); + + verify(serviceProxy).deleteService(eq("testNamespace"), eq("testService"), eq("testGroup")); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } + + @Test + void testUpdateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.updateService(serviceForm); + + verify(serviceProxy).updateService(eq(serviceForm), + eq(Service.newService("testNamespace", "testGroup", "testService")), any(ServiceMetadata.class), + any(Map.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testGetServiceDetail() throws Exception { + ServiceDetail serviceDetail = new ServiceDetail(); + + when(serviceProxy.getServiceDetail(any(String.class), any(String.class), any(String.class))).thenReturn( + serviceDetail); + + Result actual = (Result) consoleServiceController.getServiceDetail( + "testNamespace", "testService"); + + verify(serviceProxy).getServiceDetail(any(String.class), any(String.class), any(String.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(serviceDetail, actual.getData()); + } + + @Test + void testGetSelectorTypeList() throws Exception { + when(serviceProxy.getSelectorTypeList()).thenReturn(Collections.singletonList("label")); + + Result> actual = consoleServiceController.getSelectorTypeList(); + + verify(serviceProxy).getSelectorTypeList(); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(1, actual.getData().size()); + assertEquals("label", actual.getData().get(0)); + } + + @Test + void testGetSubscribers() throws Exception { + ObjectNode subscribers = new ObjectMapper().createObjectNode(); + subscribers.put("subscriber", "testSubscriber"); + + when(serviceProxy.getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyBoolean())).thenReturn( + subscribers); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("pageNo", "1"); + request.addParameter("pageSize", "10"); + request.addParameter("namespaceId", "testNamespace"); + request.addParameter("serviceName", "testService"); + request.addParameter("aggregation", "true"); + + Result actual = consoleServiceController.subscribers(request); + + verify(serviceProxy).getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(subscribers, actual.getData()); + } + + @Test + void testGetServiceList() throws Exception { + when(serviceProxy.getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyString(), anyBoolean())).thenReturn(Collections.singletonList(new ServiceDetail())); + + Result> actual = (Result>) consoleServiceController.getServiceList(true, + "testNamespace", 1, 10, "testService", "testGroup", "instance", true); + + verify(serviceProxy).getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyString(), anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(1, actual.getData().size()); + } + + @Test + void testUpdateCluster() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("namespaceId", "testNamespace"); + request.addParameter("serviceName", "testService"); + request.addParameter("clusterName", "testCluster"); + request.addParameter("checkPort", "8080"); + request.addParameter("useInstancePort4Check", "true"); + request.addParameter("healthChecker", "{\"type\":\"TCP\"}"); + request.addParameter("metadata", "{\"key\":\"value\"}"); + + Result actual = consoleServiceController.updateCluster(request); + + verify(serviceProxy).updateClusterMetadata(anyString(), anyString(), anyString(), any(ClusterMetadata.class)); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java new file mode 100644 index 00000000000..96cec0da2e1 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java @@ -0,0 +1,101 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * PermissionControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class PermissionControllerV3Test { + + @InjectMocks + private PermissionControllerV3 permissionController; + + @Mock + private NacosRoleServiceImpl nacosRoleService; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(permissionController).build(); + } + + @Test + void testGetPermissionListAccurateSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.getPermissionsFromDatabase(anyString(), anyInt(), anyInt())).thenReturn( + permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "accurate"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).getPermissionsFromDatabase("admin", 1, 10); + } + + @Test + void testGetPermissionListBlurSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.findPermissionsLike4Page(anyString(), anyInt(), anyInt())).thenReturn(permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "blur"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).findPermissionsLike4Page("admin", 1, 10); + } + + @Test + void testCreatePermission() { + Result result = (Result) permissionController.createPermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).addPermission("admin", "testResource", "write"); + assertEquals("add permission ok!", result.getData()); + } + + @Test + void testDeletePermission() { + Result result = (Result) permissionController.deletePermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).deletePermission("admin", "testResource", "write"); + assertEquals("delete permission ok!", result.getData()); + } +} \ No newline at end of file diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java new file mode 100644 index 00000000000..bfe508ecb69 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java @@ -0,0 +1,123 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * RoleControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class RoleControllerV3Test { + + @Mock + private NacosRoleServiceImpl roleService; + + @InjectMocks + private RoleControllerV3 roleControllerV3; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(roleControllerV3).build(); + } + + @Test + void testCreateRole() { + Result result = (Result) roleControllerV3.createRole("test", "nacos"); + + verify(roleService, times(1)).addRole(anyString(), anyString()); + + assertEquals("add role ok!", result.getData()); + } + + @Test + void testDeleteRoleWithoutUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", ""); + + verify(roleService, times(1)).deleteRole(anyString()); + + assertEquals("delete role of user ok!", result.getData()); + } + + @Test + void testDeleteRoleWithUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", "nacos"); + + verify(roleService, times(1)).deleteRole(anyString(), anyString()); + + assertEquals("delete role of user nacos ok!", result.getData()); + } + + @Test + void testGetRoleListAccurateSearch() { + Page rolesTest = new Page(); + + when(roleService.getRolesFromDatabase(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "accurate"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListFuzzySearch() { + Page rolesTest = new Page(); + + when(roleService.findRolesLike4Page(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "blur"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListByRoleName() { + List rolesTest = new ArrayList<>(); + + when(roleService.findRolesLikeRoleName(anyString())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleListByRoleName("test"); + + assertEquals(rolesTest, result.getData()); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java new file mode 100644 index 00000000000..72dde868773 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java @@ -0,0 +1,218 @@ +/* + * 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.config.AuthConfigs; +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.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * UserControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +class UserControllerV3Test { + + @Mock + private NacosUserDetailsServiceImpl userDetailsService; + + @Mock + private NacosRoleServiceImpl roleService; + + @Mock + private AuthConfigs authConfigs; + + @Mock + private IAuthenticationManager iAuthenticationManager; + + @Mock + private TokenManagerDelegate jwtTokenManager; + + @InjectMocks + private UserControllerV3 userControllerV3; + + private NacosUser user; + + @BeforeEach + void setUp() { + user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + } + + @Test + void testCreateUserSuccess() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(null); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.createUser("test", "testPass"); + + verify(userDetailsService, times(1)).createUser(eq("test"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$"), "Password hash should start with '$2a$10$'"); + + assertEquals("create user ok!", result.getData()); + } + + @Test + void testCreateUserUserAlreadyExists() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(new User()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.createUser("test", "testPass"); + }); + + assertEquals("user 'test' already exist!", exception.getMessage()); + } + + @Test + void testDeleteUserSuccess() { + when(roleService.getRoles("nacos")).thenReturn(new ArrayList<>()); + + Result result = (Result) userControllerV3.deleteUser("nacos"); + + verify(userDetailsService, times(1)).deleteUser("nacos"); + assertEquals("delete user ok!", result.getData()); + } + + @Test + void testDeleteUserCannotDeleteAdmin() { + List roleInfoList = new ArrayList<>(); + RoleInfo adminRole = new RoleInfo(); + adminRole.setRole(AuthConstants.GLOBAL_ADMIN_ROLE); + roleInfoList.add(adminRole); + + when(roleService.getRoles("nacos")).thenReturn(roleInfoList); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.deleteUser("nacos"); + }); + + assertEquals("cannot delete admin: nacos", exception.getMessage()); + } + + @Test + void testUpdateUserSuccess() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + when(userDetailsService.getUserFromDatabase("nacos")).thenReturn(new User()); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.updateUser("nacos", "newPass", response, request); + + verify(userDetailsService, times(1)).updateUserPassword(eq("nacos"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + assertEquals("update user ok!", result.getData()); + } + + @Test + void testLoginSuccess() throws AccessException, IOException { + NacosUser user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + MockHttpServletRequest request = new MockHttpServletRequest(); + when(iAuthenticationManager.authenticate(request)).thenReturn(user); + when(iAuthenticationManager.hasGlobalAdminRole(user)).thenReturn(true); + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(jwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(18000L); + MockHttpServletResponse response = new MockHttpServletResponse(); + Object actual = userControllerV3.login("nacos", "nacos", response, request); + + assertTrue(actual instanceof ObjectNode); + + String actualString = actual.toString(); + + assertTrue(actualString.contains("\"accessToken\":\"1234567890\"")); + assertTrue(actualString.contains("\"tokenTtl\":18000")); + assertTrue(actualString.contains("\"globalAdmin\":true")); + + assertEquals(AuthConstants.TOKEN_PREFIX + "1234567890", response.getHeader(AuthConstants.AUTHORIZATION_HEADER)); + } + + @Test + void testCreateAdminUserSuccess() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(false); + + Result result = (Result) userControllerV3.createAdminUser("testAdminPass"); + + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + verify(userDetailsService, times(1)).createUser(usernameCaptor.capture(), passwordCaptor.capture()); + + assertEquals(AuthConstants.DEFAULT_USER, usernameCaptor.getValue()); + + ObjectNode data = result.getData(); + assertEquals(AuthConstants.DEFAULT_USER, data.get(AuthConstants.PARAM_USERNAME).asText()); + assertEquals("testAdminPass", data.get(AuthConstants.PARAM_PASSWORD).asText()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + } + + @Test + void testCreateAdminUserConflict() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(true); + + Result result = (Result) userControllerV3.createAdminUser("adminPass"); + + assertEquals(HttpStatus.CONFLICT.value(), result.getCode()); + } +} + From 24359ecd80144478abdb1c98da9c63fe8c7cdd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Fri, 6 Sep 2024 18:14:05 +0800 Subject: [PATCH 11/17] [ISSUE #12017] Fix bugs * Add getAllSubClientConfigByIp method in config section * Fix bugs in AuthFilter and RemoteRequestAuthFilter * Fix bugs in ServiceProxy --- .../v3/config/ConsoleConfigController.java | 17 +++++++++- .../console/handler/config/ConfigHandler.java | 12 +++++++ .../inner/config/ConfigInnerHandler.java | 31 +++++++++++++++++++ .../console/proxy/config/ConfigProxy.java | 11 +++++++ .../console/proxy/naming/ServiceProxy.java | 1 - .../alibaba/nacos/core/auth/AuthFilter.java | 3 +- .../core/auth/RemoteRequestAuthFilter.java | 3 +- 7 files changed, 74 insertions(+), 4 deletions(-) diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java index ac446d70d04..77066001392 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java @@ -21,7 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; @@ -42,6 +42,7 @@ import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.http.ResponseEntity; +import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -291,6 +292,20 @@ public Result getListeners(@RequestParam("dataId") Stri return Result.success(configProxy.getListeners(dataId, group, namespaceId, sampleTime)); } + /** + * Get subscribe information from client side. + */ + @GetMapping("/listener/ip") + @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG) + public Result getAllSubClientConfigByIp(@RequestParam("ip") String ip, + @RequestParam(value = "all", required = false) boolean all, + @RequestParam(value = "namespaceId", required = false) String namespaceId, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime, ModelMap modelMap) + throws NacosException { + GroupkeyListenserStatus result = configProxy.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + return Result.success(result); + } + /** * Export configuration. * diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java index 00953a66dd8..d04dd935524 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java @@ -51,6 +51,7 @@ public interface ConfigHandler { * @param group The group to which the configuration belongs. * @param namespaceId The namespace identifier. * @param configAdvanceInfo Additional advanced search criteria. + * @return ConfigInfo containing all details of the specified configuration. * @throws IOException If an input or output exception occurs. * @throws ServletException If a servlet-specific exception occurs. * @throws NacosException If an error related to Nacos configuration occurs. @@ -161,6 +162,17 @@ Page getConfigListByContent(String search, int pageNo, int pageSize, GroupkeyListenserStatus getListeners(String dataId, String group, String namespaceId, int sampleTime) throws Exception; + /** + * Get subscription information based on IP, tenant, and other parameters. + * + * @param ip IP address of the client + * @param all Whether to retrieve all configurations + * @param namespaceId Tenant information + * @param sampleTime Sample time for the subscription + * @return GroupkeyListenserStatus object containing subscription information + */ + GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime); + /** * Imports and publishes a configuration from a file. * diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java index a58b5e4a7f0..a32407cf57a 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/config/ConfigInnerHandler.java @@ -42,6 +42,7 @@ import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.config.server.utils.YamlParserUtil; import com.alibaba.nacos.config.server.utils.ZipUtils; @@ -195,6 +196,36 @@ public GroupkeyListenserStatus getListeners(String dataId, String group, String return gls; } + @Override + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime) { + SampleResult collectSampleResult = configSubService.getCollectSampleResultByIp(ip, sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + Map configMd5Status = new HashMap<>(100); + + if (collectSampleResult.getLisentersGroupkeyStatus() == null) { + return gls; + } + + Map status = collectSampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry config : status.entrySet()) { + if (!StringUtils.isBlank(namespaceId) && config.getKey().contains(namespaceId)) { + configMd5Status.put(config.getKey(), config.getValue()); + continue; + } + if (all) { + configMd5Status.put(config.getKey(), config.getValue()); + } else { + String[] configKeys = GroupKey2.parseKey(config.getKey()); + if (StringUtils.isBlank(configKeys[2])) { + configMd5Status.put(config.getKey(), config.getValue()); + } + } + } + gls.setLisentersGroupkeyStatus(configMd5Status); + return gls; + } + @Override public ResponseEntity exportConfig(String dataId, String group, String namespaceId, String appName, List ids) throws Exception { diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java index 2f9447becdd..db3e6a4b52c 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java @@ -141,6 +141,17 @@ public GroupkeyListenserStatus getListeners(String dataId, String group, String return configHandler.getListeners(dataId, group, namespaceId, sampleTime); } + /** + * Get subscription information based on IP, tenant, and other parameters. + */ + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime) throws NacosException { + ConfigHandler configHandler = configHandlerMap.get(consoleConfig.getType()); + if (configHandler == null) { + throw new NacosException(NacosException.INVALID_PARAM, "Invalid deployment type"); + } + return configHandler.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + } + /** * Export configuration. */ diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java index d4c0632a9dc..a6215f3fe11 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -19,7 +19,6 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.console.config.ConsoleConfig; -import com.alibaba.nacos.console.handler.core.ClusterHandler; import com.alibaba.nacos.console.handler.inner.naming.ServiceInnerHandler; import com.alibaba.nacos.console.handler.naming.ServiceHandler; import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java index 75bb9d93d08..9dd406960da 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -109,7 +109,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } - if (method.isAnnotationPresent(Secured.class) && (authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled())) { + boolean isAuthEnabled = authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled(); + if (method.isAnnotationPresent(Secured.class) && isAuthEnabled) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java index cb520b104c6..626f2b986f9 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java @@ -63,7 +63,8 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th try { Method method = getHandleMethod(handlerClazz); - if (method.isAnnotationPresent(Secured.class) && (authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled())) { + boolean isAuthEnabled = authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled(); + if (method.isAnnotationPresent(Secured.class) && isAuthEnabled) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {}", request.getClass().getSimpleName()); From 633f7cfc7f23b2084b4fb2cdffde3ae0fae51b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 10 Sep 2024 12:06:43 +0800 Subject: [PATCH 12/17] [ISSUE #12017] Add language validate * Add language validate --- .../controller/v3/ConsoleServerStateController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java index 50f9746e42d..4da4d95d9fe 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java @@ -26,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; +import java.util.List; import java.util.Map; /** @@ -64,6 +66,11 @@ public Result> serverState() { @GetMapping("/announcement") public Result getAnnouncement( @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { + // Validate the language parameter + List supportedLanguages = Arrays.asList("zh-CN", "en-US"); + if (!supportedLanguages.contains(language)) { + return Result.failure("Unsupported language: " + language); + } String announcement = serverStateProxy.getAnnouncement(language); return Result.success(announcement); } From a260de01b6bd8ec29c33a21dcabdef3b9ab94d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 10 Sep 2024 17:46:19 +0800 Subject: [PATCH 13/17] [ISSUE #12017] Add validation operations * Add validation operations --- .../v3/naming/ConsoleServiceController.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java index 7cc17bce54d..e3876f10e23 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -48,6 +48,7 @@ import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -111,6 +112,7 @@ public Result deleteService( @RequestParam("serviceName") String serviceName, @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName) throws Exception { + checkServiceName(serviceName); serviceProxy.deleteService(namespaceId, serviceName, groupName); return Result.success("ok"); } @@ -227,6 +229,7 @@ public Object getServiceList(@RequestParam(required = false) boolean withInstanc @GetMapping() public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, String serviceName) throws NacosException { + checkServiceName(serviceName); String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); String groupName = NamingUtils.getGroupName(serviceName); return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName)); @@ -246,7 +249,6 @@ public Result updateCluster(HttpServletRequest request) throws Exception Constants.DEFAULT_NAMESPACE_ID); final String clusterName = WebUtils.required(request, CommonParams.CLUSTER_NAME); final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); - ClusterMetadata clusterMetadata = new ClusterMetadata(); clusterMetadata.setHealthyCheckPort(NumberUtils.toInt(WebUtils.required(request, "checkPort"))); clusterMetadata.setUseInstancePortForCheck( @@ -262,4 +264,11 @@ public Result updateCluster(HttpServletRequest request) throws Exception return Result.success("ok"); } + private void checkServiceName(String serviceName) throws NacosApiException { + if (StringUtils.isBlank(serviceName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'serviceName' type String is not present"); + } + } + } From 78549f198ec0b4a32fc1b1b2d3436b8e5bd87e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 10 Sep 2024 17:49:55 +0800 Subject: [PATCH 14/17] [ISSUE #12017] Fix namespace update method * Fix namespace update method --- .../v3/core/ConsoleNamespaceController.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java index 86da70d8678..8b003d3cd3f 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java @@ -51,7 +51,7 @@ * @author zhangyukun on:2024/8/27 */ @RestController -@RequestMapping("/v3/console/namespace") +@RequestMapping("/v3/console/core/namespace") @ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) public class ConsoleNamespaceController { @@ -95,20 +95,17 @@ public Result getNamespaceDetail(@RequestParam("namespaceId") String /** * create namespace. - * - * @param namespaceForm namespaceForm. + * @param namespaceId custom namespace id + * @param namespaceName custom namespace name + * @param namespaceDesc custom namespace description * @return whether create ok */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) - public Result createNamespace(NamespaceForm namespaceForm) throws NacosException { - - namespaceForm.validate(); - - String namespaceId = namespaceForm.getNamespaceId(); - String namespaceName = namespaceForm.getNamespaceName(); - String namespaceDesc = namespaceForm.getNamespaceDesc(); + public Result createNamespace(@RequestParam("customNamespaceId") String namespaceId, + @RequestParam("namespaceName") String namespaceName, + @RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) throws NacosException { if (StringUtils.isBlank(namespaceId)) { namespaceId = UUID.randomUUID().toString(); @@ -125,7 +122,7 @@ public Result createNamespace(NamespaceForm namespaceForm) throws Nacos // check unique if (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, - "the namespaceId is existed, namespaceId: " + namespaceForm.getNamespaceId()); + "the namespaceId is existed, namespaceId: " + namespaceId); } } // contains illegal chars From 1761ee8fc90fb8e25456c01a03921a493288db96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 10 Sep 2024 17:51:46 +0800 Subject: [PATCH 15/17] [ISSUE #12017] Add config error codes * Add config error codes --- .../alibaba/nacos/api/model/v2/ErrorCode.java | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java index 53b4c35735f..c20fc28024d 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java @@ -183,7 +183,46 @@ public enum ErrorCode { /** * server error. */ - SERVER_ERROR(30000, "server error"); + SERVER_ERROR(30000, "server error"), + + /** + * Config use 100001 ~ 100999. + **/ + METADATA_ILLEGAL(100002, "导入的元数据非法"), + + DATA_VALIDATION_FAILED(100003, "未读取到合法数据"), + + PARSING_DATA_FAILED(100004, "解析数据失败"), + + DATA_EMPTY(100005, "导入的文件数据为空"), + + NO_SELECTED_CONFIG(100006, "没有选择任何配置"), + + /** + * Conflict error. + */ + CONFLICT(409, "Conflict"), + + /** + * Not Implemented error. + */ + NOT_IMPLEMENTED(501, "Not Implemented"), + + /** + * Authorization failed error. + */ + FORBIDDEN(403, "Authorization failed!"), + + /** + * Unauthorized error. + */ + UNAUTHORIZED(401, "Unauthorized"), + + /** + * Internal Server Error. + */ + INTERNAL_SERVER_ERROR(500, "Internal Server Error"); + private final Integer code; From 0b4e2b0b62bd577d6417d0fcae8e6663c5833b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Tue, 24 Sep 2024 15:25:14 +0800 Subject: [PATCH 16/17] [ISSUE #12017] Optimize API * ApiType checking and adding * Add groupName to Service's API. * Modify the status code specified by HTTP --- api/pom.xml | 6 +- .../alibaba/nacos/api/model/v2/ErrorCode.java | 28 +-------- .../alibaba/nacos/api/model/v2/Result.java | 10 ++++ .../nacos/api/model/v2/SupportedLanguage.java | 60 +++++++++++++++++++ .../v3/ConsoleServerStateController.java | 6 +- .../v3/config/ConsoleConfigController.java | 2 +- .../v3/core/ConsoleClusterController.java | 4 +- .../v3/core/ConsoleNamespaceController.java | 9 +-- .../v3/naming/ConsoleInstanceController.java | 20 +++++-- .../v3/naming/ConsoleServiceController.java | 18 +++--- .../inner/naming/ServiceInnerHandler.java | 21 ++++--- .../handler/naming/ServiceHandler.java | 17 +++--- .../console/proxy/naming/ServiceProxy.java | 11 ++-- .../core/ConsoleNamespaceControllerTest.java | 8 +-- .../naming/ConsoleServiceControllerTest.java | 7 ++- .../nacos/naming/core/SubscribeManager.java | 21 ++++++- .../impl/controller/v3/UserControllerV3.java | 8 +-- 17 files changed, 168 insertions(+), 88 deletions(-) create mode 100644 api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java diff --git a/api/pom.xml b/api/pom.xml index 45b3ae08302..47850806e17 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -96,6 +96,10 @@ javax.annotation javax.annotation-api - + + org.springframework + spring-web + + diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java index c20fc28024d..0fd8d0013c8 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java @@ -196,33 +196,7 @@ public enum ErrorCode { DATA_EMPTY(100005, "导入的文件数据为空"), - NO_SELECTED_CONFIG(100006, "没有选择任何配置"), - - /** - * Conflict error. - */ - CONFLICT(409, "Conflict"), - - /** - * Not Implemented error. - */ - NOT_IMPLEMENTED(501, "Not Implemented"), - - /** - * Authorization failed error. - */ - FORBIDDEN(403, "Authorization failed!"), - - /** - * Unauthorized error. - */ - UNAUTHORIZED(401, "Unauthorized"), - - /** - * Internal Server Error. - */ - INTERNAL_SERVER_ERROR(500, "Internal Server Error"); - + NO_SELECTED_CONFIG(100006, "没有选择任何配置"); private final Integer code; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java index 98ae78c5f73..2d6e1e75cc3 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.api.model.v2; import java.io.Serializable; +import org.springframework.http.HttpStatus; /** * Response Result. @@ -96,6 +97,15 @@ public static Result failure(ErrorCode errorCode, T data) { return new Result<>(errorCode.getCode(), errorCode.getMsg(), data); } + /** + * Failed return with httpStatus, message and data. + * @param data type + * @return Result + */ + public static Result failure(HttpStatus httpStatus, T data) { + return new Result<>(httpStatus.value(), httpStatus.getReasonPhrase(), data); + } + @Override public String toString() { return "Result{" + "errorCode=" + code + ", message='" + message + '\'' + ", data=" + data + '}'; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java new file mode 100644 index 00000000000..8ca862cef5f --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java @@ -0,0 +1,60 @@ +/* + * 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.api.model.v2; + +/** + * Supported languages for announcements. + * + * @author zhangyukun on:2024/9/24 + */ +public enum SupportedLanguage { + /** + * Chinese language. + */ + ZH_CN("zh-CN"), + + /** + * English language. + */ + EN_US("en-US"); + + private final String language; + + SupportedLanguage(String language) { + this.language = language; + } + + public String getLanguage() { + return language; + } + + /** + * Check if the given language is supported. + * + * @param language the language to check + * @return true if the language is supported, false otherwise + */ + public static boolean isSupported(String language) { + for (SupportedLanguage lang : SupportedLanguage.values()) { + if (lang.getLanguage().equals(language)) { + return true; + } + } + return false; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java index 4da4d95d9fe..a2eb4c28839 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java @@ -18,6 +18,7 @@ package com.alibaba.nacos.console.controller.v3; import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.model.v2.SupportedLanguage; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.ServerStateProxy; import com.alibaba.nacos.core.paramcheck.ExtractorManager; @@ -26,8 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.Arrays; -import java.util.List; import java.util.Map; /** @@ -67,8 +66,7 @@ public Result> serverState() { public Result getAnnouncement( @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { // Validate the language parameter - List supportedLanguages = Arrays.asList("zh-CN", "en-US"); - if (!supportedLanguages.contains(language)) { + if (!SupportedLanguage.isSupported(language)) { return Result.failure("Unsupported language: " + language); } String announcement = serverStateProxy.getAnnouncement(language); diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java index 77066001392..82752d28314 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java @@ -296,7 +296,7 @@ public Result getListeners(@RequestParam("dataId") Stri * Get subscribe information from client side. */ @GetMapping("/listener/ip") - @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG) + @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) public Result getAllSubClientConfigByIp(@RequestParam("ip") String ip, @RequestParam(value = "all", required = false) boolean all, @RequestParam(value = "namespaceId", required = false) String namespaceId, diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java index 6f5f6b6f85a..ef35895d4fe 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.core.ClusterProxy; import com.alibaba.nacos.core.cluster.Member; @@ -61,7 +62,8 @@ public ConsoleClusterController(ClusterProxy clusterProxy) { * @return all members */ @GetMapping(value = "/nodes") - @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result> getNodeList(@RequestParam(value = "keyword", required = false) String ipKeyWord) { Collection result = clusterProxy.getNodeList(ipKeyWord); return Result.success(result); diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java index 8b003d3cd3f..f15d230529d 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.enums.ApiType; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.core.NamespaceProxy; @@ -88,7 +89,7 @@ public Result> getNamespaceList() throws NacosException { */ @GetMapping() @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result getNamespaceDetail(@RequestParam("namespaceId") String namespaceId) throws NacosException { return Result.success(namespaceProxy.getNamespaceDetail(namespaceId)); } @@ -102,7 +103,7 @@ public Result getNamespaceDetail(@RequestParam("namespaceId") String */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result createNamespace(@RequestParam("customNamespaceId") String namespaceId, @RequestParam("namespaceName") String namespaceName, @RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) throws NacosException { @@ -141,7 +142,7 @@ public Result createNamespace(@RequestParam("customNamespaceId") String */ @PutMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result updateNamespace(NamespaceForm namespaceForm) throws NacosException { namespaceForm.validate(); // contains illegal chars @@ -160,7 +161,7 @@ public Result updateNamespace(NamespaceForm namespaceForm) throws Nacos */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX - + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) public Result deleteNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { return Result.success(namespaceProxy.deleteNamespace(namespaceId)); } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java index 6402641ec9a..6cfb0c3ede0 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.enums.ApiType; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.console.proxy.naming.InstanceProxy; import com.alibaba.nacos.core.control.TpsControl; @@ -82,12 +83,12 @@ public ConsoleInstanceController(InstanceProxy instanceProxy, SwitchDomain switc @RequestMapping("/list") public Result getInstanceList( @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, - @RequestParam String serviceName, @RequestParam(required = false) Boolean healthyOnly, - @RequestParam(required = false) Boolean enabledOnly, @RequestParam(name = "pageNo") int page, - @RequestParam int pageSize) { - String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); - String groupName = NamingUtils.getGroupName(serviceName); - ObjectNode result = instanceProxy.listInstances(namespaceId, serviceNameWithoutGroup, groupName, page, pageSize, healthyOnly, enabledOnly); + @RequestParam String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName, + @RequestParam(required = false) Boolean healthyOnly, @RequestParam(required = false) Boolean enabledOnly, + @RequestParam(name = "pageNo") int page, @RequestParam int pageSize) throws NacosApiException { + checkServiceName(serviceName); + ObjectNode result = instanceProxy.listInstances(namespaceId, serviceName, groupName, page, pageSize, + healthyOnly, enabledOnly); return Result.success(result); } @@ -134,4 +135,11 @@ private Instance buildInstance(InstanceForm instanceForm) throws NacosException private String buildCompositeServiceName(InstanceForm instanceForm) { return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); } + + private void checkServiceName(String serviceName) throws NacosApiException { + if (StringUtils.isBlank(serviceName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'serviceName' type String is not present"); + } + } } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java index e3876f10e23..baa339ef309 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -25,7 +25,6 @@ import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker; import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; -import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.Selector; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.enums.ApiType; @@ -163,12 +162,13 @@ public Result subscribers(HttpServletRequest request) throws Excepti String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); + String groupName = WebUtils.optional(request, CommonParams.GROUP_NAME, Constants.DEFAULT_GROUP); + boolean aggregation = Boolean.parseBoolean( WebUtils.optional(request, "aggregation", String.valueOf(Boolean.TRUE))); - return Result.success(serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation)); - //下放参数 pageNo, pageSize, namespaceId, serviceName, aggregation - + return Result.success( + serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation)); } private Selector parseSelector(String selectorJsonString) throws Exception { @@ -228,11 +228,11 @@ public Object getServiceList(@RequestParam(required = false) boolean withInstanc @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) @GetMapping() public Object getServiceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, - String serviceName) throws NacosException { + @RequestParam("serviceName") String serviceName, + @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName) + throws NacosException { checkServiceName(serviceName); - String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); - String groupName = NamingUtils.getGroupName(serviceName); - return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName)); + return Result.success(serviceProxy.getServiceDetail(namespaceId, serviceName, groupName)); } /** @@ -259,7 +259,7 @@ public Result updateCluster(HttpServletRequest request) throws Exception clusterMetadata.setHealthyCheckType(healthChecker.getType()); clusterMetadata.setExtendData( UtilsAndCommons.parseMetadata(WebUtils.optional(request, "metadata", StringUtils.EMPTY))); - + serviceProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); return Result.success("ok"); } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java index 548f68f37ec..bf280df90ae 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/inner/naming/ServiceInnerHandler.java @@ -30,13 +30,13 @@ import com.alibaba.nacos.naming.core.SubscribeManager; import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.misc.Loggers; import com.alibaba.nacos.naming.model.form.ServiceForm; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.selector.SelectorManager; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @@ -46,7 +46,7 @@ * * @author zhangyukun */ -@Service +@org.springframework.stereotype.Service public class ServiceInnerHandler implements ServiceHandler { private final ServiceOperatorV2Impl serviceOperatorV2; @@ -61,7 +61,8 @@ public class ServiceInnerHandler implements ServiceHandler { @Autowired public ServiceInnerHandler(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager, - CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, ClusterOperatorV2Impl clusterOperatorV2) { + CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, + ClusterOperatorV2Impl clusterOperatorV2) { this.serviceOperatorV2 = serviceOperatorV2; this.selectorManager = selectorManager; this.catalogServiceV2 = catalogServiceV2; @@ -100,15 +101,17 @@ public List getSelectorTypeList() { } @Override - public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, boolean aggregation) throws Exception { + Service service = Service.newService(namespaceId, groupName, serviceName); + ObjectNode result = JacksonUtils.createEmptyJsonNode(); int count = 0; try { - List subscribers = subscribeManager.getSubscribers(serviceName, namespaceId, aggregation); + List subscribers = subscribeManager.getSubscribers(service, aggregation); int start = (pageNo - 1) * pageSize; if (start < 0) { @@ -144,13 +147,13 @@ public Object getServiceList(boolean withInstances, String namespaceId, int page } @Override - public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) - throws NacosException { - return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceNameWithoutGroup); + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { + return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceName); } @Override - public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, ClusterMetadata clusterMetadata) throws Exception { + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); } diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java index 404191f9388..8fcd8a89488 100644 --- a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java @@ -37,7 +37,7 @@ public interface ServiceHandler { /** * Create a new service. * - * @param serviceForm the service form containing the service details + * @param serviceForm the service form containing the service details * @param serviceMetadata the service metadata created from serviceForm * @throws Exception if an error occurs during service creation */ @@ -59,7 +59,7 @@ public interface ServiceHandler { * @param serviceForm the service form containing the service details * @param service the service object created from serviceForm * @param serviceMetadata the service metadata created from serviceForm - * @param metadata the service metadata + * @param metadata the service metadata * @throws Exception if an error occurs during service update */ void updateService(ServiceForm serviceForm, Service service, ServiceMetadata serviceMetadata, @@ -79,12 +79,13 @@ void updateService(ServiceForm serviceForm, Service service, ServiceMetadata ser * @param pageSize the size of the page * @param namespaceId the namespace ID * @param serviceName the service name + * @param groupName the group name * @param aggregation whether to aggregate the results * @return a JSON node containing the list of subscribers * @throws Exception if an error occurs during fetching subscribers */ - ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, boolean aggregation) - throws Exception; + ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, + boolean aggregation) throws Exception; /** * List service detail information. @@ -106,13 +107,13 @@ Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int /** * Get the detail of a specific service. * - * @param namespaceId the namespace ID - * @param serviceNameWithoutGroup the service name without group - * @param groupName the group name + * @param namespaceId the namespace ID + * @param serviceName the service name without group + * @param groupName the group name * @return service detail information * @throws NacosException if an error occurs during fetching service details */ - Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) throws NacosException; + Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException; /** * Update the metadata of a cluster. diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java index a6215f3fe11..94b874f7bda 100644 --- a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -123,17 +123,18 @@ public List getSelectorTypeList() { * @param pageSize the size of the page * @param namespaceId the namespace ID * @param serviceName the service name + * @param groupName the group name * @param aggregation whether to aggregate the results * @return a JSON node containing the list of subscribers * @throws Exception if an error occurs during fetching subscribers */ - public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, boolean aggregation) throws Exception { ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); if (serviceHandler == null) { throw new IllegalArgumentException("Invalid deployment type"); } - return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, aggregation); + return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation); } /** @@ -164,18 +165,18 @@ public Object getServiceList(boolean withInstances, String namespaceId, int page * Retrieves the details of a specific service by delegating the operation to the appropriate handler. * * @param namespaceId the namespace ID - * @param serviceNameWithoutGroup the service name without group + * @param serviceName the service name without group * @param groupName the group name * @return service detail information * @throws NacosException if an error occurs during fetching service details */ - public Object getServiceDetail(String namespaceId, String serviceNameWithoutGroup, String groupName) + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { ServiceHandler serviceHandler = serviceHandlerMap.get(consoleConfig.getType()); if (serviceHandler == null) { throw new IllegalArgumentException("Invalid deployment type"); } - return serviceHandler.getServiceDetail(namespaceId, serviceNameWithoutGroup, groupName); + return serviceHandler.getServiceDetail(namespaceId, serviceName, groupName); } /** diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java index cc32123520d..09b78047300 100644 --- a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java @@ -77,7 +77,7 @@ void testGetNamespaceList() throws Exception { List namespaces = Arrays.asList(namespace); when(namespaceProxy.getNamespaceList()).thenReturn(namespaces); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace/list"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/list"); MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); String actualValue = response.getContentAsString(); @@ -98,7 +98,7 @@ void testGetNamespaceDetail() throws Exception { when(namespaceProxy.getNamespaceDetail(anyString())).thenReturn(namespace); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace") + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace") .param("namespaceId", "testNamespace"); MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); @@ -133,7 +133,7 @@ void testUpdateNamespace() throws Exception { void testDeleteNamespace() throws Exception { when(namespaceProxy.deleteNamespace(anyString())).thenReturn(true); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/namespace") + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/core/namespace") .param("namespaceId", "testNamespace"); MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); @@ -144,7 +144,7 @@ void testDeleteNamespace() throws Exception { void testCheckNamespaceIdExist() throws Exception { when(namespaceProxy.checkNamespaceIdExist(anyString())).thenReturn(true); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/namespace/exist") + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/exist") .param("customNamespaceId", "testNamespace"); MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java index 61e791eb6a0..199f49856c7 100644 --- a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java @@ -135,7 +135,7 @@ void testGetServiceDetail() throws Exception { serviceDetail); Result actual = (Result) consoleServiceController.getServiceDetail( - "testNamespace", "testService"); + "testNamespace", "testService", "testGroup"); verify(serviceProxy).getServiceDetail(any(String.class), any(String.class), any(String.class)); @@ -161,7 +161,7 @@ void testGetSubscribers() throws Exception { ObjectNode subscribers = new ObjectMapper().createObjectNode(); subscribers.put("subscriber", "testSubscriber"); - when(serviceProxy.getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyBoolean())).thenReturn( + when(serviceProxy.getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), anyBoolean())).thenReturn( subscribers); MockHttpServletRequest request = new MockHttpServletRequest(); @@ -169,11 +169,12 @@ void testGetSubscribers() throws Exception { request.addParameter("pageSize", "10"); request.addParameter("namespaceId", "testNamespace"); request.addParameter("serviceName", "testService"); + request.addParameter("groupName", "testGroup"); request.addParameter("aggregation", "true"); Result actual = consoleServiceController.subscribers(request); - verify(serviceProxy).getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyBoolean()); + verify(serviceProxy).getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), anyBoolean()); assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); assertEquals(subscribers, actual.getData()); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java index 9f47ae0a489..5e65da610c6 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java @@ -16,12 +16,12 @@ package com.alibaba.nacos.naming.core; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.NamingSubscriberServiceAggregationImpl; import com.alibaba.nacos.naming.push.NamingSubscriberServiceLocalImpl; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; @@ -40,7 +40,7 @@ * @author xiweng.yy * @since 1.0.1 */ -@Service +@org.springframework.stereotype.Service public class SubscribeManager { @Autowired @@ -67,6 +67,23 @@ public List getSubscribers(String serviceName, String namespaceId, b } } + /** + * Get subscribers. + * + * @param service service info + * @param aggregation aggregation + * @return list of subscriber + */ + public List getSubscribers(Service service, boolean aggregation) { + if (aggregation) { + Collection result = aggregationService.getFuzzySubscribers(service); + return CollectionUtils.isNotEmpty(result) ? result.stream().filter(distinctByKey(Subscriber::toString)) + .collect(Collectors.toList()) : Collections.EMPTY_LIST; + } else { + return new LinkedList<>(localService.getFuzzySubscribers(service)); + } + } + public static Predicate distinctByKey(Function keyExtractor) { Map seen = new ConcurrentHashMap<>(128); return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java index 7dbf06cfcb1..28b3071139d 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java @@ -18,7 +18,6 @@ package com.alibaba.nacos.plugin.auth.impl.controller.v3; 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.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; @@ -42,6 +41,7 @@ import com.alibaba.nacos.plugin.auth.impl.utils.PasswordEncoderUtil; import com.alibaba.nacos.plugin.auth.impl.utils.PasswordGeneratorUtil; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -138,7 +138,7 @@ public Object createAdminUser(@RequestParam(required = false) String password) { if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { if (iAuthenticationManager.hasGlobalAdminRole()) { - return Result.failure(ErrorCode.CONFLICT, "have admin user cannot use it"); + return Result.failure(HttpStatus.CONFLICT, "have admin user cannot use it"); } String username = AuthConstants.DEFAULT_USER; userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); @@ -148,7 +148,7 @@ public Object createAdminUser(@RequestParam(required = false) String password) { result.put(AuthConstants.PARAM_PASSWORD, password); return Result.success(result); } else { - return Result.failure(ErrorCode.NOT_IMPLEMENTED, "not support"); + return Result.failure(HttpStatus.NOT_IMPLEMENTED, "not support"); } } @@ -322,7 +322,7 @@ public Object login(@RequestParam String username, @RequestParam String password response.addHeader(AuthConstants.AUTHORIZATION_HEADER, "Bearer " + token); return Result.success("Bearer " + token); } catch (BadCredentialsException authentication) { - return Result.failure(ErrorCode.UNAUTHORIZED, "Login failed"); + return Result.failure(HttpStatus.UNAUTHORIZED, "Login failed"); } } } From 92e4845a6f2b4a39109fbfad85148340c175adbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=85=9C=E5=9D=A4?= Date: Wed, 25 Sep 2024 15:49:39 +0800 Subject: [PATCH 17/17] [ISSUE #12017] Update the frontend of the console by using the Console API for config section * Update the request path * Update the request parameters * Update the return values --- .../EditorNameSpace/EditorNameSpace.js | 19 ++++++---- .../components/NameSpaceList/NameSpaceList.js | 4 +- .../components/NewNameSpace/NewNameSpace.js | 10 +++-- .../ClusterNodeList/ClusterNodeList.js | 4 +- .../ConfigDetail/ConfigCompared.js | 4 +- .../ConfigDetail/ConfigDetail.js | 28 +++++++------- .../ConfigEditor/ConfigEditor.js | 12 +++--- .../ConfigEditor/NewConfigEditor.js | 18 ++++----- .../ConfigRollback/ConfigRollback.js | 16 ++++---- .../ConfigurationManagement.js | 38 +++++++++---------- .../HistoryDetail/HistoryDetail.js | 4 +- .../HistoryRollback/HistoryRollback.js | 24 ++++++------ .../ListeningToQuery/ListeningToQuery.js | 12 +++--- .../NewConfig/NewConfig.js | 30 +++++++++------ 14 files changed, 119 insertions(+), 104 deletions(-) diff --git a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js index 5e1af4a796d..1f96be34380 100644 --- a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js +++ b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js @@ -72,8 +72,9 @@ class EditorNameSpace extends React.Component { this.field.setValues(record); request({ type: 'get', - url: `v1/console/namespaces?show=all&namespaceId=${record.namespace}`, + url: `v3/console/core/namespace?namespaceId=${record.namespace}`, success: res => { + res = res.data; if (res !== null) { this.field.setValue('namespaceDesc', res.namespaceDesc); } else { @@ -101,15 +102,18 @@ class EditorNameSpace extends React.Component { beforeSend: () => { this.openLoading(); }, - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', data: { - namespace: values.namespace, - namespaceShowName: values.namespaceShowName, + namespaceId: values.namespace, + namespaceName: values.namespaceShowName, namespaceDesc: values.namespaceDesc, }, success: res => { - if (res === true) { + // res = res.data; + console.log(3); + if (res.code === 0) { + res = res.data; this.closeDialog(); this.props.getNameSpaces(); this.refreshNameSpace(); // 刷新全局namespace @@ -131,9 +135,10 @@ class EditorNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + console.log(res); + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/components/NameSpaceList/NameSpaceList.js b/console-ui/src/components/NameSpaceList/NameSpaceList.js index 825337cfd53..8fe772cf634 100644 --- a/console-ui/src/components/NameSpaceList/NameSpaceList.js +++ b/console-ui/src/components/NameSpaceList/NameSpaceList.js @@ -111,9 +111,9 @@ class NameSpaceList extends React.Component { } else { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { this.handleNameSpaces(res.data); } else { Dialog.alert({ diff --git a/console-ui/src/components/NewNameSpace/NewNameSpace.js b/console-ui/src/components/NewNameSpace/NewNameSpace.js index 4f50e0c07d7..40b628e58e5 100644 --- a/console-ui/src/components/NewNameSpace/NewNameSpace.js +++ b/console-ui/src/components/NewNameSpace/NewNameSpace.js @@ -111,13 +111,14 @@ class NewNameSpace extends React.Component { } request({ type: 'get', - url: 'v1/console/namespaces?checkNamespaceIdExist=true', + url: 'v3/console/core/namespace/exist', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { customNamespaceId, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -130,7 +131,7 @@ class NewNameSpace extends React.Component { } else { request({ type: 'post', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { @@ -139,6 +140,7 @@ class NewNameSpace extends React.Component { namespaceDesc: values.namespaceDesc, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -167,9 +169,9 @@ class NewNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js index 51092c9fa1a..7860b793811 100644 --- a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js +++ b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js @@ -84,7 +84,7 @@ class ClusterNodeList extends React.Component { `keyword=${keyword}`, ]; request({ - url: `v1/core/cluster/nodes?${parameter.join('&')}`, + url: `v3/console/core/cluster/nodes?${parameter.join('&')}`, beforeSend: () => this.openLoading(), success: ({ count = 0, data = [] } = {}) => { this.setState({ @@ -107,7 +107,7 @@ class ClusterNodeList extends React.Component { const accessToken = JSON.parse(localStorage.token || '{}').accessToken; this.openLoading(); axios - .post(`v1/core/cluster/server/leave?accessToken=${accessToken}`, nodes) + .post(`v3/console/core/cluster/server/leave?accessToken=${accessToken}`, nodes) .then(response => { if (response.data.code === 200) { Message.success(locale.leaveSucc); diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js index 21c97ae2235..bf876e5423b 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js @@ -58,9 +58,9 @@ class ConfigCompared extends React.Component { getNamespaces() { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { const { namespacesDataSource } = this.state; this.setState({ namespacesDataSource: res.data }); } else { diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js index 59db9acfa39..3c87212384f 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js @@ -66,7 +66,7 @@ class ConfigDetail extends React.Component { this.group = getParams('group') || 'DEFAULT_GROUP'; this.ips = ''; this.valueMap = {}; // 存储不同版本的数据 - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.searchDataId = getParams('searchDataId') || ''; this.searchGroup = getParams('searchGroup') || ''; this.pageSize = getParams('pageSize'); @@ -147,18 +147,18 @@ class ConfigDetail extends React.Component { const { locale = {} } = this.props; const self = this; this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.edasAppName = getParams('edasAppName') || ''; this.inApp = this.edasAppName; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?&dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); @@ -186,7 +186,7 @@ class ConfigDetail extends React.Component { serverId: this.serverId, group: this.searchGroup, dataId: this.searchDataId, - namespace: this.tenant, + namespace: this.namespaceId, pageNo: this.pageNo, pageSize: this.pageSize, }) @@ -226,7 +226,7 @@ class ConfigDetail extends React.Component { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); - let url = `v1/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&group=${this.group}`; + let url = `v3/console/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { @@ -234,6 +234,7 @@ class ConfigDetail extends React.Component { }, success(result) { if (result != null) { + const result = result.data; let rightvalue = result.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); @@ -248,18 +249,19 @@ class ConfigDetail extends React.Component { }); } - openCompare = ([dataId, group, tenant]) => { + openCompare = ([dataId, group, namespaceId]) => { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); const params = { - show: 'all', + // show: 'all', group, dataId, - tenant, + namespaceId, }; - requestUtils.get('v1/cs/configs', { params }).then(res => { - if (res != null && res !== '') { + requestUtils.get('v3/console/cs/config', { params }).then(res => { + if (res != null && res !== '' && res.code === 0) { + res = res.data; let rightvalue = res.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); @@ -319,7 +321,7 @@ class ConfigDetail extends React.Component { )}

-

{this.tenant}

+

{this.namespaceId}

diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js index e1d181a14f7..97fe5d8c2fb 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js @@ -184,15 +184,15 @@ class ConfigEditor extends React.Component { const self = this; this.tenant = getParams('namespace') || ''; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?dataId=${this.dataId}&group=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); // self.field.setValue('content', data.content); @@ -369,12 +369,12 @@ class ConfigEditor extends React.Component { appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), group: this.field.getValue('group'), desc: this.field.getValue('desc'), - config_tags: this.state.config_tags.join(','), + configTags: this.state.config_tags.join(','), type: this.state.configType, content, - tenant: this.tenant, + namespaceId: this.tenant, }; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded', diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js index a8fdce74e7b..607a2bc5feb 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js @@ -264,13 +264,13 @@ class ConfigEditor extends React.Component { const stringify = require('qs/lib/stringify'); this.setState({ loading: true }); return request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', method: 'post', data: stringify(payload), headers, }).then( res => { - if (res) { + if (res.data) { if (isNewConfig) { this.setState({ isNewConfig: false }); } @@ -312,14 +312,14 @@ class ConfigEditor extends React.Component { stopBeta() { const { dataId, group } = this.state.form; - const tenant = getParams('namespace'); + const namespaceId = getParams('namespace'); return request - .delete('v1/cs/configs', { + .delete('v3/console/cs/config', { params: { beta: true, dataId, group, - tenant, + namespaceId, }, }) .then(res => { @@ -398,7 +398,7 @@ class ConfigEditor extends React.Component { } else { params.show = 'all'; } - return request.get('v1/cs/configs', { params }).then(res => { + return request.get('v3/console/cs/config', { params }).then(res => { const form = beta ? res.data : res; if (!form) return false; const { type, content, configTags, betaIps, md5 } = form; @@ -424,15 +424,15 @@ class ConfigEditor extends React.Component { tenant: namespace, }; // get subscribes of the namespace - return request.get('v1/cs/configs/listener', { params }).then(res => { + return request.get('v3/console/cs/config/listener', { params }).then(res => { const { subscriberDataSource } = this.state; - const lisentersGroupkeyIpMap = res.lisentersGroupkeyStatus; + const lisentersGroupkeyIpMap = res.data.lisentersGroupkeyStatus; if (lisentersGroupkeyIpMap) { this.setState({ subscriberDataSource: subscriberDataSource.concat(Object.keys(lisentersGroupkeyIpMap)), }); } - return res; + return res.data; }); } diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js index 9d058df05e5..120d79513cf 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js @@ -73,14 +73,14 @@ class ConfigRollback extends React.Component { getDataDetail() { const self = this; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || ''; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`; + const url = `v3/console/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`; request({ url, success(result) { if (result != null) { - const data = result; + const data = result.data; const envName = self.serverId; self.id = data.id; // 详情的id self.field.setValue('dataId', data.dataId); @@ -143,12 +143,12 @@ class ConfigRollback extends React.Component { dataId: self.dataId, group: self.group, content: self.field.getValue('content'), - tenant: self.tenant, + namespaceId: self.tenant, }; - let url = 'v1/cs/configs'; + let url = 'v3/console/cs/config'; if (self.opType.trim() === 'I') { - url = `v1/cs/configs?dataId=${self.dataId}&group=${self.group}`; + url = `v3/console/cs/config?dataId=${self.dataId}&group=${self.group}`; postData = {}; } @@ -158,8 +158,8 @@ class ConfigRollback extends React.Component { contentType: 'application/x-www-form-urlencoded', url, data: postData, - success(data) { - if (data === true) { + success(res) { + if (res.data === true) { Dialog.alert({ content: locale.rollbackSuccessful }); } }, diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js index 7c02c3b269e..fdc6be61605 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js @@ -279,7 +279,7 @@ class ConfigurationManagement extends React.Component { config_tags: this.state.config_tags.join(','), pageNo: prePageNo ? prePageNo : pageNo, pageSize: prePageSize ? prePageSize : this.state.pageSize, - tenant: this.tenant, + namespaceId: this.tenant, }; setParams('pageSize', null); setParams('pageNo', null); @@ -294,6 +294,7 @@ class ConfigurationManagement extends React.Component { } props = this.props.getConfigsV2(params); } else { + console.log('Params before request:', params); props = this.props.getConfigs(params); } props @@ -362,7 +363,7 @@ class ConfigurationManagement extends React.Component { ), onOk: () => { - const url = `v1/cs/configs?dataId=${record.dataId}&group=${record.group}`; + const url = `v3/console/cs/config?dataId=${record.dataId}&group=${record.group}`; request({ url, type: 'delete', @@ -579,9 +580,8 @@ class ConfigurationManagement extends React.Component { exportData() { const { group, appName, dataId, openUri } = this; const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); - openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), + openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), group, appName, dataId, @@ -594,9 +594,8 @@ class ConfigurationManagement extends React.Component { exportDataNew() { const { group, appName, dataId, openUri } = this; const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); - openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), + openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), group, appName, dataId, @@ -619,9 +618,8 @@ class ConfigurationManagement extends React.Component { } configsTableSelected.forEach((value, key, map) => ids.push(key)); if (newVersion) { - this.openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), + this.openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), group: '', appName: '', ids: ids.join(','), @@ -629,9 +627,8 @@ class ConfigurationManagement extends React.Component { username, }); } else { - this.openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), + this.openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), group: '', appName: '', ids: ids.join(','), @@ -670,9 +667,9 @@ class ConfigurationManagement extends React.Component { ), onOk: () => { const url = - `v1/cs/configs?delType=ids&ids=${Array.from(configsTableSelected.keys()).join( + `v3/console/cs/config/batchDelete?&ids=${Array.from(configsTableSelected.keys()).join( ',' - )}&tenant=` + self.state.nownamespace_id; + )}&namespaceId=` + self.state.nownamespace_id; request({ url, type: 'delete', @@ -699,13 +696,14 @@ class ConfigurationManagement extends React.Component { return; } request({ - url: 'v1/console/namespaces?namespaceId=', + url: 'v3/console/core/namespace?namespaceId=', beforeSend() { self.openLoading(); }, success(data) { + data = data.data self.closeLoading(); - if (!data || data.code !== 200 || !data.data) { + if (!data || data.code !== 0 || !data.data) { Dialog.alert({ title: locale.getNamespaceFailed, content: locale.getNamespaceFailed, @@ -855,7 +853,7 @@ class ConfigurationManagement extends React.Component { let cloneTargetSpace = self.field.getValue('cloneTargetSpace'); let sameConfigPolicy = self.field.getValue('sameConfigPolicy'); request({ - url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`, + url: `v3/console/cs/config/clone?namespaceId=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`, method: 'post', data: JSON.stringify(clonePostData), contentType: 'application/json', @@ -1037,7 +1035,7 @@ class ConfigurationManagement extends React.Component { const { accessToken = '', username = '' } = token; const uploadProps = { accept: 'application/zip', - action: `v1/cs/configs?import=true&namespace=${getParams( + action: `v3/console/cs/config/import?namespaceId=${getParams( 'namespace' )}&accessToken=${accessToken}&username=${username}&tenant=${getParams('namespace')}`, headers: Object.assign({}, {}, { accessToken }), diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js index 19b0cecb763..78080e3651a 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js @@ -61,10 +61,10 @@ class HistoryDetail extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`, + url: `v3/console/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`, success(result) { if (result != null) { - const data = result; + const data = result.data; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); self.field.setValue('appName', self.inApp ? self.edasAppName : data.appName); diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js index 4fe7f28d98b..5d6746ba291 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js @@ -115,9 +115,10 @@ class HistoryRollback extends React.Component { beforeSend() { self.openLoading(); }, - url: `v1/cs/history?search=accurate&dataId=${this.state.dataId}&group=${this.state.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, - success(data) { - if (data != null) { + url: `v3/console/cs/history/list?dataId=${this.state.dataId}&group=${this.state.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, + success(res) { + if (res != null) { + const data = res.data self.setState({ dataSource: data.pageItems || [], total: data.totalCount, @@ -234,9 +235,9 @@ class HistoryRollback extends React.Component { return new Promise((resolve, reject) => { const { locale = {} } = this.props; const self = this; - this.tenant = tenant; + this.namespaceId = tenant; this.serverId = tenant; - const url = `v1/cs/configs?show=all&dataId=${dataId}&group=${group}`; + const url = `v3/console/cs/config?dataId=${dataId}&group=${group}`; request({ url, beforeSend() { @@ -244,7 +245,7 @@ class HistoryRollback extends React.Component { }, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, complete() { @@ -266,10 +267,10 @@ class HistoryRollback extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${dataId}&group=${group}&nid=${nid}`, + url: `v3/console/cs/history?dataId=${dataId}&group=${group}&nid=${nid}`, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, }); @@ -291,9 +292,10 @@ class HistoryRollback extends React.Component { this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 const self = this; request({ - url: `v1/cs/history/configs?tenant=${this.tenant}`, - success(result) { - if (result != null) { + url: `v3/console/cs/history/configs?namespaceId=${this.tenant}`, + success(res) { + if (res != null) { + const result = res.data; const dataIdList = []; const groupList = []; for (let i = 0; i < result.length; i++) { diff --git a/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js b/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js index 66e14396115..04802407bac 100644 --- a/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js +++ b/console-ui/src/pages/ConfigurationManagement/ListeningToQuery/ListeningToQuery.js @@ -92,26 +92,26 @@ class ListeningToQuery extends React.Component { const type = this.getValue('type'); if (type === 1) { const ip = this.getValue('ip'); - queryUrl = `v1/cs/listener?ip=${ip}`; + queryUrl = `v3/console/cs/config/listener/ip?ip=${ip}`; const tenant = window.nownamespace || getParams('namespace') || ''; if (tenant) { - queryUrl += `&tenant=${tenant}`; + queryUrl += `&namespaceId=${tenant}`; } } else { const dataId = this.getValue('dataId'); const group = this.getValue('group'); if (!dataId || !group) return false; - queryUrl = `v1/cs/configs/listener?dataId=${dataId}&group=${group}`; + queryUrl = `v3/console/cs/config/listener?dataId=${dataId}&group=${group}`; } request({ url: queryUrl, beforeSend() { self.openLoading(); }, - success(data) { - if (data.collectStatus === 200) { + success(res) { + if (res.code === 0) { const dataSoureTmp = []; - const status = data.lisentersGroupkeyStatus; + const status = res.data; for (const key in status) { if (type === 1) { const obj = {}; diff --git a/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js b/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js index 0109d364e9b..b19bb9cd516 100644 --- a/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js +++ b/console-ui/src/pages/ConfigurationManagement/NewConfig/NewConfig.js @@ -297,22 +297,28 @@ class NewConfig extends React.Component { const { locale = {} } = this.props; const { addonBefore } = this.state; request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', data: { - show: 'all', dataId: addonBefore + this.field.getValue('dataId'), group: this.field.getValue('group'), - tenant: getParams('namespace') || '', + namespaceId: getParams('namespace') || '', }, success: res => { - // 返回成功 说明存在就不提交配置 - Message.error({ - content: locale.dataIdExists, - align: 'cc cc', - }); + // 检查 res.data 是否为 null,如果不是 null,说明存在就不提交配置 + if (res.data !== null) { + console.log('Data exists, not submitting config'); // 输出提示信息 + Message.error({ + content: locale.dataIdExists, + align: 'cc cc', + }); + } else { + console.log('Data does not exist, proceeding to publish config'); // 输出提示信息 + // 如果 res.data 为 null,表示没有数据,可以继续处理 + this._publishConfig(content); + } }, error: err => { - // 后端接口很不规范 响应为空 说明没有数据 就可以新增 + // 后端接口很不规范,响应为空,说明没有数据,可以新增 const { status } = err || {}; if (status === 403) { Dialog.alert({ @@ -335,13 +341,13 @@ class NewConfig extends React.Component { group: this.field.getValue('group'), content, desc: this.field.getValue('desc'), - config_tags: config_tags.join(), + configTags: config_tags.join(), type: configType, appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), - tenant: this.tenant, + namespaceId: this.tenant, }; this.serverId = getParams('serverId') || 'center'; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded',