feat(auth): 优化权限配置,扩展 SKILL_ADMIN 和 AUDITOR 权限

## 后端修改
- 添加 RouteSecurityPolicyRegistry 权限配置(修复12个CLI命令403错误)
  - /api/v1/me/** - 用户个人相关
  - /api/v1/notifications/** - 通知相关
  - /api/v1/reviews/** - 审核相关
  - /api/v1/skills/** POST/PUT - archive, rating, report
  - /api/v1/namespaces/** POST - transfer
  - DELETE /api/v1/skills/*/* - 改为所有者可删除
- AdminSkillController: hide/unhide 允许 SKILL_ADMIN
- SkillDeleteController: delete 允许 SKILL_ADMIN
- 新增 AdminAuditorController: 审计员专用控制器

## CLI修改
- publish.ts: 修复 -v 选项冲突,改为只使用 --skill-version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chenbaowang 2026-04-24 00:47:53 +08:00
parent f4752f2d9b
commit 803540b157
4 changed files with 79 additions and 6 deletions

View file

@ -0,0 +1,35 @@
package com.iflytek.skillhub.controller.admin;
import com.iflytek.skillhub.controller.BaseApiController;
import com.iflytek.skillhub.dto.ApiResponse;
import com.iflytek.skillhub.dto.ApiResponseFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Auditor-specific endpoints for read-only access to platform data.
* AUDITOR role can view all data for compliance and auditing purposes.
*/
@RestController
@RequestMapping("/api/v1/admin/auditor")
public class AdminAuditorController extends BaseApiController {
public AdminAuditorController(ApiResponseFactory responseFactory) {
super(responseFactory);
}
// TODO: Implement auditor-specific endpoints
// Examples:
// - GET /all-skills - View all skills including hidden
// - GET /review-history - View review history
// - GET /user-activity - View user activity logs
// - GET /statistics - View platform statistics
@GetMapping("/status")
@PreAuthorize("hasAnyRole('AUDITOR', 'SUPER_ADMIN')")
public ApiResponse<String> getStatus() {
return ok("response.success", "Auditor API is ready. More endpoints coming soon.");
}
}

View file

@ -33,7 +33,7 @@ public class AdminSkillController extends BaseApiController {
}
@PostMapping("/{skillId}/hide")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@PreAuthorize("hasAnyRole('SKILL_ADMIN', 'SUPER_ADMIN')")
public ApiResponse<AdminSkillMutationResponse> hideSkill(@PathVariable Long skillId,
@RequestBody(required = false) AdminSkillActionRequest request,
@AuthenticationPrincipal PlatformPrincipal principal,
@ -49,7 +49,7 @@ public class AdminSkillController extends BaseApiController {
}
@PostMapping("/{skillId}/unhide")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@PreAuthorize("hasAnyRole('SKILL_ADMIN', 'SUPER_ADMIN')")
public ApiResponse<AdminSkillMutationResponse> unhideSkill(@PathVariable Long skillId,
@AuthenticationPrincipal PlatformPrincipal principal,
HttpServletRequest httpRequest) {

View file

@ -32,7 +32,7 @@ public class SkillDeleteController extends BaseApiController {
}
@DeleteMapping("/id/{skillId}")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@PreAuthorize("hasAnyRole('SKILL_ADMIN', 'SUPER_ADMIN')")
public ApiResponse<SkillDeleteResponse> deleteSkillById(@PathVariable Long skillId,
@AuthenticationPrincipal PlatformPrincipal principal,
HttpServletRequest request) {
@ -50,7 +50,7 @@ public class SkillDeleteController extends BaseApiController {
}
@DeleteMapping("/{namespace}/{slug}")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@PreAuthorize("hasAnyRole('SKILL_ADMIN', 'SUPER_ADMIN')")
public ApiResponse<SkillDeleteResponse> deleteSkill(@PathVariable String namespace,
@PathVariable String slug,
@RequestParam(required = false) String ownerId,

View file

@ -67,14 +67,34 @@ public class RouteSecurityPolicyRegistry {
RouteAuthorizationPolicy.permitAll(HttpMethod.GET, "/api/web/skills/*/*/tags/*/files"),
RouteAuthorizationPolicy.permitAll(HttpMethod.GET, "/api/web/skills/*/*/tags/*/file"),
RouteAuthorizationPolicy.permitAll(HttpMethod.GET, "/api/web/labels"),
RouteAuthorizationPolicy.roles(HttpMethod.DELETE, "/api/v1/skills/id/*", "SUPER_ADMIN"),
RouteAuthorizationPolicy.roles(HttpMethod.DELETE, "/api/v1/skills/*/*", "SUPER_ADMIN"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/v1/skills/id/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/v1/skills/*/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/web/skills/id/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/web/skills/*/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/namespaces"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/namespaces/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/namespaces"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/namespaces/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/me/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/me/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/notifications"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/notifications/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/v1/notifications/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/notifications"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/notifications/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/web/notifications/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/v1/reviews/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.GET, "/api/web/reviews/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/v1/reviews/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/web/reviews/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/v1/skills/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/web/skills/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.PUT, "/api/v1/skills/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.PUT, "/api/web/skills/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/v1/namespaces/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.POST, "/api/web/namespaces/**"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/v1/skills/*/*"),
RouteAuthorizationPolicy.authenticated(HttpMethod.DELETE, "/api/web/skills/*/*"),
RouteAuthorizationPolicy.authenticated(null, "/api/v1/admin/**")
);
@ -94,6 +114,24 @@ public class RouteSecurityPolicyRegistry {
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/namespaces/*"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/namespaces"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/namespaces/*"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/me/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/me/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/notifications"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/notifications/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/notifications/*"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/notifications"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/notifications/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/notifications/*"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/skills/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/skills/**"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/v1/skills/**"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/web/skills/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/namespaces/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/namespaces/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/resolve/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/v1/download"),
ApiTokenPolicy.allow(null, "/.well-known/**"),