fix(portal): keep skill detail on viewer permissions

This commit is contained in:
xiose 2026-04-14 16:42:22 +08:00
parent a1e4904d97
commit edcc248244
4 changed files with 66 additions and 35 deletions

View file

@ -70,12 +70,10 @@ public class SkillController extends BaseApiController {
@PathVariable String namespace,
@PathVariable String slug,
@RequestAttribute(value = "userId", required = false) String userId,
@RequestAttribute(value = "userNsRoles", required = false) Map<Long, NamespaceRole> userNsRoles,
@RequestAttribute(value = "platformRoles", required = false) java.util.Set<String> platformRoles) {
@RequestAttribute(value = "userNsRoles", required = false) Map<Long, NamespaceRole> userNsRoles) {
SkillQueryService.SkillDetailDTO detail = skillQueryService.getSkillDetail(
namespace, slug, userId, userNsRoles != null ? userNsRoles : Map.of(),
platformRoles != null ? platformRoles : java.util.Set.<String>of());
namespace, slug, userId, userNsRoles != null ? userNsRoles : Map.of());
SkillDetailResponse response = new SkillDetailResponse(
detail.id(),

View file

@ -149,8 +149,7 @@ class SkillControllerTest {
eq("team"),
eq("demo"),
eq((String) null),
eq(Map.<Long, NamespaceRole>of()),
org.mockito.ArgumentMatchers.<java.util.Set<String>>any()))
eq(Map.<Long, NamespaceRole>of())))
.thenReturn(new SkillQueryService.SkillDetailDTO(
1L,
"demo",
@ -197,8 +196,7 @@ class SkillControllerTest {
eq("team"),
eq("demo"),
eq((String) null),
eq(Map.<Long, NamespaceRole>of()),
org.mockito.ArgumentMatchers.<java.util.Set<String>>any()))
eq(Map.<Long, NamespaceRole>of())))
.thenThrow(new DomainForbiddenException("error.namespace.archived", "team"));
mockMvc.perform(get("/api/web/skills/team/demo"))

View file

@ -150,28 +150,15 @@ public class SkillQueryService {
String skillSlug,
String currentUserId,
Map<Long, NamespaceRole> userNsRoles) {
return getSkillDetail(namespaceSlug, skillSlug, currentUserId, userNsRoles, Set.of());
}
public SkillDetailDTO getSkillDetail(
String namespaceSlug,
String skillSlug,
String currentUserId,
Map<Long, NamespaceRole> userNsRoles,
Set<String> platformRoles) {
Namespace namespace = findNamespace(namespaceSlug);
Skill skill = resolveVisibleSkill(namespace.getId(), skillSlug, currentUserId);
// Archived namespace: only members (or super admins) may view
if (namespace.getStatus() == com.iflytek.skillhub.domain.namespace.NamespaceStatus.ARCHIVED
&& !isNamespaceMember(namespace.getId(), currentUserId, userNsRoles)
&& !isSuperAdmin(platformRoles)) {
&& !isNamespaceMember(namespace.getId(), currentUserId, userNsRoles)) {
throw new DomainForbiddenException("error.namespace.archived", namespaceSlug);
}
// Visibility check
if (!visibilityChecker.canAccess(skill, currentUserId, userNsRoles, platformRoles)) {
if (!visibilityChecker.canAccess(skill, currentUserId, userNsRoles)) {
throw new DomainForbiddenException("error.skill.access.denied", skillSlug);
}
@ -203,7 +190,7 @@ public class SkillQueryService {
skill.getNamespaceId(),
skill.getCreatedAt(),
skill.getUpdatedAt(),
canManageRestrictedSkill(skill, currentUserId, userNsRoles, platformRoles),
canManageRestrictedSkill(skill, currentUserId, userNsRoles),
canSubmitPromotion(namespace, skill, publishedVersion, currentUserId, userNsRoles),
headlineVersion == null || "PUBLISHED".equals(headlineVersion.status()),
currentUserId == null || !Objects.equals(skill.getOwnerId(), currentUserId),
@ -215,6 +202,15 @@ public class SkillQueryService {
);
}
public SkillDetailDTO getSkillDetail(
String namespaceSlug,
String skillSlug,
String currentUserId,
Map<Long, NamespaceRole> userNsRoles,
Set<String> platformRoles) {
return getSkillDetail(namespaceSlug, skillSlug, currentUserId, userNsRoles);
}
/**
* Lists skills within a namespace after filtering out records the caller is
* not allowed to discover.
@ -647,13 +643,6 @@ public class SkillQueryService {
}
private boolean canManageRestrictedSkill(Skill skill, String currentUserId, Map<Long, NamespaceRole> userNsRoles) {
return canManageRestrictedSkill(skill, currentUserId, userNsRoles, Set.of());
}
private boolean canManageRestrictedSkill(Skill skill, String currentUserId, Map<Long, NamespaceRole> userNsRoles, Set<String> platformRoles) {
if (platformRoles != null && platformRoles.contains("SUPER_ADMIN")) {
return true;
}
if (currentUserId == null) {
return false;
}
@ -695,10 +684,6 @@ public class SkillQueryService {
return currentUserId != null && userNsRoles.containsKey(namespaceId);
}
private boolean isSuperAdmin(Set<String> platformRoles) {
return platformRoles != null && platformRoles.contains("SUPER_ADMIN");
}
private String resolveOwnerPreviewReviewComment(SkillLifecycleProjectionService.VersionProjection ownerPreviewVersion) {
if (ownerPreviewVersion == null || !"REJECTED".equals(ownerPreviewVersion.status())) {
return null;

View file

@ -771,6 +771,56 @@ class SkillQueryServiceTest {
assertFalse(result.canSubmitPromotion());
}
@Test
void testGetSkillDetail_ShouldNotGrantLifecyclePermissionToSuperAdminInPortal() throws Exception {
String namespaceSlug = "test-ns";
String skillSlug = "test-skill";
String userId = "super-1";
Map<Long, NamespaceRole> userNsRoles = Map.of(1L, NamespaceRole.MEMBER);
Namespace namespace = new Namespace(namespaceSlug, "Test NS", "owner-1");
setId(namespace, 1L);
Skill skill = new Skill(1L, skillSlug, "owner-1", SkillVisibility.PUBLIC);
setId(skill, 1L);
skill.setStatus(SkillStatus.ACTIVE);
skill.setLatestVersionId(11L);
SkillVersion published = new SkillVersion(1L, "1.0.0", "owner-1");
setId(published, 11L);
published.setStatus(SkillVersionStatus.PUBLISHED);
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(List.of(skill));
when(skillVersionRepository.findById(11L)).thenReturn(Optional.of(published));
SkillQueryService.SkillDetailDTO result = service.getSkillDetail(
namespaceSlug, skillSlug, userId, userNsRoles, Set.of("SUPER_ADMIN"));
assertFalse(result.canManageLifecycle());
assertFalse(result.canSubmitPromotion());
assertEquals("PUBLISHED", result.resolutionMode());
}
@Test
void testGetSkillDetail_ShouldNotGrantPrivateVisibilityToSuperAdminInPortal() throws Exception {
String namespaceSlug = "test-ns";
String skillSlug = "test-skill";
String userId = "super-1";
Namespace namespace = new Namespace(namespaceSlug, "Test NS", "owner-1");
setId(namespace, 1L);
Skill skill = new Skill(1L, skillSlug, "owner-1", SkillVisibility.PRIVATE);
setId(skill, 1L);
skill.setStatus(SkillStatus.ACTIVE);
skill.setLatestVersionId(11L);
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(List.of(skill));
assertThrows(DomainForbiddenException.class, () ->
service.getSkillDetail(namespaceSlug, skillSlug, userId, Map.of(), Set.of("SUPER_ADMIN")));
}
@Test
void testGetSkillDetail_ShouldPreferPendingVersionForOwnerPreview() throws Exception {
String namespaceSlug = "test-ns";