This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 6a1a0e6753 fix(admin): apply namespace name filter for administrators.
(#7728)
6a1a0e6753 is described below
commit 6a1a0e67532ffba6a121f941d9ffa9c486bd61e9
Author: 甜梦守护者 <[email protected]>
AuthorDate: Fri Oct 23 11:39:30 2026 +0800
fix(admin): apply namespace name filter for administrators. (#7728)
---
.../admin/service/impl/NamespaceServiceImpl.java | 4 +-
.../shenyu/admin/mapper/NamespaceMapperTest.java | 107 +++++++++++++++++++++
.../admin/service/NamespaceServiceImplTest.java | 96 +++++++++++++++++-
3 files changed, 205 insertions(+), 2 deletions(-)
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/NamespaceServiceImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/NamespaceServiceImpl.java
index 2fdc41c93c..1a28a07f23 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/NamespaceServiceImpl.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/NamespaceServiceImpl.java
@@ -183,7 +183,9 @@ public class NamespaceServiceImpl implements
NamespaceService {
public List<NamespaceVO> list(final String name) {
if (SessionUtil.isAdmin()) {
- List<NamespaceDO> allList = namespaceMapper.selectAll();
+ NamespaceQuery query = new NamespaceQuery();
+ query.setName(name);
+ List<NamespaceDO> allList = namespaceMapper.selectByQuery(query);
return
allList.stream().map(NamespaceTransfer.INSTANCE::mapToVo).collect(Collectors.toList());
}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/NamespaceMapperTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/NamespaceMapperTest.java
new file mode 100644
index 0000000000..71691d62bb
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/NamespaceMapperTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.mapper;
+
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.shenyu.admin.model.entity.NamespaceDO;
+import org.apache.shenyu.admin.model.query.NamespaceQuery;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for namespace name filtering and namespace scope.
+ */
+public final class NamespaceMapperTest {
+
+ @Test
+ public void testNameFilteringAndNamespaceScope() throws Exception {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource(
+ "jdbc:h2:mem:namespace-filter-" + UUID.randomUUID()
+ + ";DB_CLOSE_DELAY=-1;MODE=MySQL", "sa", "");
+ JdbcTemplate jdbc = new JdbcTemplate(dataSource);
+
+ try {
+ new ResourceDatabasePopulator(
+ new
ClassPathResource("sql-script/h2/schema.sql")).execute(dataSource);
+
+ String insertSql = "INSERT INTO namespace (id, namespace_id, name)
VALUES (?, ?, ?)";
+ jdbc.update(insertSql, "test-foo", "ns-foo", "pr7703-foo");
+ jdbc.update(insertSql, "test-prod-foo", "ns-prod-foo",
"prod-pr7703-foo");
+ jdbc.update(insertSql, "test-bar", "ns-bar", "pr7703-bar");
+
+ Configuration configuration = new Configuration(
+ new Environment("test", new JdbcTransactionFactory(),
dataSource));
+ configuration.setMapUnderscoreToCamelCase(true);
+ String mapperResource = "mappers/namespace-sqlmap.xml";
+ try (InputStream input = new
ClassPathResource(mapperResource).getInputStream()) {
+ new XMLMapperBuilder(input, configuration, mapperResource,
+ configuration.getSqlFragments()).parse();
+ }
+
+ try (SqlSession session = new SqlSessionFactoryBuilder()
+ .build(configuration).openSession()) {
+ NamespaceMapper mapper =
session.getMapper(NamespaceMapper.class);
+ NamespaceQuery query = new NamespaceQuery();
+ query.setName("pr7703-foo");
+
+ assertEquals(Set.of("ns-foo", "ns-prod-foo"),
+ namespaceIds(mapper.selectByQuery(query)));
+
+ List<String> allowedNamespaces = List.of("ns-foo", "ns-bar");
+ assertEquals(Set.of("ns-foo"), namespaceIds(
+ mapper.selectByNamespaceIdsAndName(allowedNamespaces,
"pr7703-foo")));
+
+ query.setName("pr7703-missing");
+ assertTrue(mapper.selectByQuery(query).isEmpty());
+
+ Set<String> allNamespaces = namespaceIds(mapper.selectAll());
+ for (String name : new String[]{null, ""}) {
+ query.setName(name);
+ assertEquals(allNamespaces,
namespaceIds(mapper.selectByQuery(query)));
+ assertEquals(Set.of("ns-foo", "ns-bar"), namespaceIds(
+
mapper.selectByNamespaceIdsAndName(allowedNamespaces, name)));
+ }
+ }
+ } finally {
+ jdbc.execute("SHUTDOWN");
+ }
+ }
+
+ private Set<String> namespaceIds(final List<NamespaceDO> namespaces) {
+ return namespaces.stream()
+ .map(NamespaceDO::getNamespaceId)
+ .collect(Collectors.toSet());
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceServiceImplTest.java
index b4906def9e..075e12b303 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceServiceImplTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceServiceImplTest.java
@@ -28,13 +28,19 @@ import org.apache.shenyu.admin.mapper.RuleMapper;
import org.apache.shenyu.admin.mapper.SelectorMapper;
import org.apache.shenyu.admin.model.entity.DiscoveryDO;
import org.apache.shenyu.admin.model.entity.NamespaceDO;
+import org.apache.shenyu.admin.model.query.NamespaceQuery;
+import org.apache.shenyu.admin.model.vo.NamespaceVO;
import org.apache.shenyu.admin.service.impl.NamespaceServiceImpl;
import org.apache.shenyu.admin.service.publish.NamespaceEventPublisher;
+import org.apache.shenyu.admin.utils.SessionUtil;
import org.apache.shenyu.admin.utils.ShenyuResultMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
@@ -42,13 +48,16 @@ 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.anyList;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
- * Tests for namespace deletion with discovery records.
+ * Tests for namespace listing and deletion.
*/
@ExtendWith(MockitoExtension.class)
public final class NamespaceServiceImplTest {
@@ -125,4 +134,89 @@ public final class NamespaceServiceImplTest {
verify(discoveryMapper).selectAllByNamespaceIds(namespaceIds);
verify(namespaceMapper).deleteByIds(ids);
}
+
+ @Test
+ public void testAdminListFiltersByName() {
+ NamespaceQuery query = new NamespaceQuery();
+ query.setName("foo");
+
+ NamespaceDO namespace = NamespaceDO.builder()
+ .namespaceId("namespace-foo")
+ .name("prod-foo")
+ .build();
+
+ try (MockedStatic<SessionUtil> sessionUtil =
mockStatic(SessionUtil.class)) {
+ sessionUtil.when(SessionUtil::isAdmin).thenReturn(true);
+
when(namespaceMapper.selectByQuery(query)).thenReturn(List.of(namespace));
+
+ List<NamespaceVO> result = namespaceService.list("foo");
+
+ assertEquals(1, result.size());
+ assertEquals("namespace-foo", result.get(0).getNamespaceId());
+ assertEquals("prod-foo", result.get(0).getName());
+
+ verify(namespaceMapper).selectByQuery(query);
+ verify(namespaceMapper, never()).selectAll();
+ }
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ public void testAdminListWithoutNameReturnsAll(final String name) {
+ NamespaceQuery query = new NamespaceQuery();
+ query.setName(name);
+
+ List<NamespaceDO> namespaces = List.of(
+
NamespaceDO.builder().namespaceId("namespace-foo").name("foo").build(),
+
NamespaceDO.builder().namespaceId("namespace-bar").name("bar").build());
+
+ try (MockedStatic<SessionUtil> sessionUtil =
mockStatic(SessionUtil.class)) {
+ sessionUtil.when(SessionUtil::isAdmin).thenReturn(true);
+ when(namespaceMapper.selectByQuery(query)).thenReturn(namespaces);
+
+ List<NamespaceVO> result = namespaceService.list(name);
+
+ assertEquals(List.of("foo", "bar"),
result.stream().map(NamespaceVO::getName).toList());
+ verify(namespaceMapper).selectByQuery(query);
+ }
+ }
+
+ @Test
+ public void testNonAdminListUsesAuthorizedNamespaces() {
+ List<String> namespaceIds = List.of("namespace-foo");
+ NamespaceDO namespace = NamespaceDO.builder()
+ .namespaceId("namespace-foo")
+ .name("prod-foo")
+ .build();
+
+ try (MockedStatic<SessionUtil> sessionUtil =
mockStatic(SessionUtil.class)) {
+ sessionUtil.when(SessionUtil::isAdmin).thenReturn(false);
+ sessionUtil.when(SessionUtil::visitorId).thenReturn("user-1");
+
when(namespaceUserService.listNamespaceIdByUserId("user-1")).thenReturn(namespaceIds);
+ when(namespaceMapper.selectByNamespaceIdsAndName(namespaceIds,
"foo"))
+ .thenReturn(List.of(namespace));
+
+ List<NamespaceVO> result = namespaceService.list("foo");
+
+ assertEquals(1, result.size());
+ assertEquals("namespace-foo", result.get(0).getNamespaceId());
+ verify(namespaceMapper).selectByNamespaceIdsAndName(namespaceIds,
"foo");
+ verify(namespaceMapper, never()).selectAll();
+ }
+ }
+
+ @Test
+ public void testNonAdminListWithoutPermissionsReturnsEmpty() {
+ try (MockedStatic<SessionUtil> sessionUtil =
mockStatic(SessionUtil.class)) {
+ sessionUtil.when(SessionUtil::isAdmin).thenReturn(false);
+ sessionUtil.when(SessionUtil::visitorId).thenReturn("user-1");
+ when(namespaceUserService.listNamespaceIdByUserId("user-1"))
+ .thenReturn(Collections.emptyList());
+
+ List<NamespaceVO> result = namespaceService.list("foo");
+
+ assertTrue(result.isEmpty());
+ verifyNoInteractions(namespaceMapper);
+ }
+ }
}