Scert/backend/internal/httpapi/project_resources_api.go

129 lines
4.8 KiB
Go

package httpapi
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
"git.pcux.cn/xuans/Scert/backend/internal/repository"
)
func (s *Server) registerProjectResourceRoutes() {
api := s.router.Group("/api/v1", privateProjectResponse, s.requireAuthenticated)
api.GET("/domains", s.listDomains)
api.GET("/domains/:id/dns-account", s.getDomainDNSAccount)
api.PUT("/domains/:id/dns-account", s.requireSameOrigin, s.setDomainDNSAccount)
api.GET("/dns/accounts", s.listDNSAccounts)
// 服务商目录不包含账户数据;项目 DNS 管理员不必拥有全局管理员身份。
api.GET("/dns/providers", s.listAdminDNSProviders)
api.GET("/dns/accounts/:id", s.getManagedDNSAccount)
api.POST("/dns/accounts", s.requireSameOrigin, s.createManagedDNSAccount)
api.PUT("/dns/accounts/:id", s.requireSameOrigin, s.renameManagedDNSAccount)
api.PUT("/dns/accounts/:id/status", s.requireSameOrigin, s.setManagedDNSAccountStatus)
api.PUT("/dns/accounts/:id/credentials", s.requireSameOrigin, s.rotateManagedDNSAccountCredential)
api.POST("/dns/accounts/:id/verify", s.requireSameOrigin, s.dnsOperation, s.verifyManagedDNSAccount)
api.GET("/audit-logs", s.listAuditLogs)
api.GET("/servers", s.listServers)
api.GET("/servers/:id", s.getServer)
api.GET("/servers/:id/domains", s.listServerDomains)
api.GET("/domains/:id/servers", s.listDomainServers)
api.GET("/certificates", s.listCertificates)
api.GET("/certificates/:id", s.getCertificate)
api.GET("/domains/:id/certificate/versions", s.listCertificateVersions)
}
func (s *Server) requireProjectResourceReader(c *gin.Context) bool {
if s.projectResources == nil {
s.writeError(c, http.StatusServiceUnavailable, "项目资源服务尚未就绪", nil)
return false
}
return true
}
func (s *Server) writeProjectResourceError(c *gin.Context, err error) {
if errors.Is(err, repository.ErrProjectAccessDenied) {
// 无权限和不存在使用相同响应,不泄露其他项目的资源是否存在。
s.writeError(c, http.StatusForbidden, "无权读取此项目资源", nil)
return
}
s.writeError(c, http.StatusInternalServerError, "读取项目资源失败,请稍后重试", nil)
}
func (s *Server) requireVisibleCertificateDomain(c *gin.Context) bool {
if !s.requireProjectResourceReader(c) {
return false
}
if _, err := s.projectResources.GetVisibleDomain(c.Request.Context(), c.GetString(authenticatedActorKey), c.Param("id")); err != nil {
s.writeProjectResourceError(c, err)
return false
}
return true
}
func (s *Server) authorizeVisibleDomainPermission(c *gin.Context, permission domain.ProjectPermission) bool {
if !s.requireProjectResourceReader(c) {
return false
}
item, err := s.projectResources.GetVisibleDomain(c.Request.Context(), c.GetString(authenticatedActorKey), c.Param("id"))
if err != nil {
s.writeProjectResourceError(c, err)
return false
}
return s.authorizeProjectPermissions(c, item.ProjectID, permission)
}
func (s *Server) authorizeVisibleDomainPermissions(c *gin.Context, permissions ...domain.ProjectPermission) bool {
if !s.requireProjectResourceReader(c) {
return false
}
item, err := s.projectResources.GetVisibleDomain(c.Request.Context(), c.GetString(authenticatedActorKey), c.Param("id"))
if err != nil {
s.writeProjectResourceError(c, err)
return false
}
return s.authorizeProjectPermissions(c, item.ProjectID, permissions...)
}
func (s *Server) authorizeProjectPermissions(c *gin.Context, projectID string, permissions ...domain.ProjectPermission) bool {
authorizer, ok := s.projectDomains.(repository.ProjectAuthorizer)
if !ok {
if fallback, fallbackOK := s.projectMembers.(repository.ProjectAuthorizer); fallbackOK {
authorizer = fallback
ok = true
}
}
if !ok {
s.writeProjectResourceError(c, errors.New("project authorizer unavailable"))
return false
}
for _, permission := range permissions {
if err := authorizer.AuthorizeProject(c.Request.Context(), c.GetString(authenticatedActorKey), projectID, permission); err != nil {
s.writeProjectResourceError(c, err)
return false
}
}
return true
}
func (s *Server) authorizeDNSCredentialUse(c *gin.Context, projectID string) bool {
authorizer, ok := s.projectMembers.(repository.ProjectAuthorizer)
if !ok {
authorizer, ok = s.projectDomains.(repository.ProjectAuthorizer)
}
if !ok {
s.writeError(c, http.StatusServiceUnavailable, "项目 DNS 权限服务尚未就绪", nil)
return false
}
if err := authorizer.AuthorizeProject(c.Request.Context(), c.GetString(authenticatedActorKey), projectID, domain.PermissionDNSCredentialUse); err != nil {
if errors.Is(err, repository.ErrProjectAccessDenied) {
s.writeError(c, http.StatusForbidden, "无权使用此项目的 DNS 凭据", nil)
return false
}
s.writeError(c, http.StatusInternalServerError, "检查项目 DNS 凭据权限失败", err)
return false
}
return true
}