Files

168 lines
6.4 KiB
Go
Raw Permalink Normal View History

package accesscontrol
import (
"net/http"
"strings"
"github.com/Tencent/WeKnora/internal/accesspolicy"
"github.com/Tencent/WeKnora/internal/types"
"github.com/gin-gonic/gin"
)
func requiredPermission(method, path, kind string, actions ...string) string {
path = strings.TrimPrefix(path, "/api/v1/")
mutating := method != http.MethodGet && method != http.MethodHead
if strings.HasPrefix(path, "workbench/resources/") && strings.HasSuffix(path, "/export") {
return "content.deliver"
}
if method != http.MethodGet && method != http.MethodHead && len(actions) > 0 &&
(strings.Contains(path, "/actions/") || strings.HasPrefix(path, "workbench/jobs/")) {
return accesspolicy.WorkbenchActionPermission(kind, actions[0])
}
module := ""
switch {
case strings.HasPrefix(path, "access/"):
return ""
case strings.HasPrefix(path, "workbench/resources/"):
if method == http.MethodDelete {
if kind == "flows" {
return "flows.delete"
}
return "content.delete"
}
if mutating && kind == "training-maps" {
return "content.maps"
}
if kind == "flows" {
module = "flows"
} else {
module = "content"
}
case strings.HasPrefix(path, "workbench/jobs/"):
if kind == "flows" {
module = "flows"
} else {
module = "content"
}
case strings.HasPrefix(path, "digital-humans"), strings.HasPrefix(path, "im/"), strings.HasPrefix(path, "im-channels"), strings.HasPrefix(path, "wechat"):
if strings.Contains(path, "/wechat/") || strings.HasPrefix(path, "wechat") || strings.HasPrefix(path, "im/") || strings.HasPrefix(path, "im-channels") {
return "digital-humans.wechat"
}
if mutating && (strings.HasSuffix(path, "/distill") || strings.HasSuffix(path, "/trial")) {
return "digital-humans.distill"
}
if mutating && strings.HasSuffix(path, "/review") {
return "digital-humans.review"
}
if method == http.MethodDelete && !strings.Contains(path, "/bindings/") {
return "digital-humans.delete"
}
module = "digital-humans"
case path == "workbench/operations/consumption":
return "knowledge.consumption"
case strings.HasPrefix(path, "workbench/operations/memories/"):
return "agents.memory"
case strings.HasPrefix(path, "workbench/operations/accounts"), strings.HasPrefix(path, "workbench/operations/grants"), strings.HasPrefix(path, "workbench/operations/quotas"):
module = "balance"
case strings.HasPrefix(path, "knowledge-chat"), strings.HasPrefix(path, "agent-chat"), strings.HasPrefix(path, "agent/"):
module = "chat"
case strings.HasPrefix(path, "knowledge"), strings.HasPrefix(path, "shared-knowledge-bases"), strings.HasPrefix(path, "chunks"), strings.HasPrefix(path, "tags"), strings.HasPrefix(path, "faq"), strings.HasPrefix(path, "initialization/config/"), strings.HasPrefix(path, "initialization/initialize/"):
module = "knowledge"
case strings.HasPrefix(path, "agents"), strings.HasPrefix(path, "shared-agents"), strings.HasPrefix(path, "embed-channels"), strings.HasPrefix(path, "custom-agents"), strings.HasPrefix(path, "skills"), strings.HasPrefix(path, "instruction-skills"), strings.HasPrefix(path, "memory"):
if mutating {
if strings.HasPrefix(path, "skills/instructions") || strings.HasPrefix(path, "instruction-skills") {
return "agents.skills"
}
if strings.HasPrefix(path, "memory") {
return "agents.memory"
}
if method == http.MethodDelete && path == "agents/:id" {
return "agents.delete"
}
}
module = "agents"
case strings.HasPrefix(path, "sessions"), strings.HasPrefix(path, "messages"), strings.HasPrefix(path, "chat"):
module = "chat"
case strings.HasPrefix(path, "organizations"):
module = "sharing"
case path == "evaluation", strings.HasPrefix(path, "tenants/kv/") && mutating, strings.HasPrefix(path, "tenants/") && (strings.Contains(path, "/members") || strings.Contains(path, "/invitations")), strings.HasPrefix(path, "me/env-vars"):
module = "settings"
case strings.HasPrefix(path, "models"), strings.HasPrefix(path, "mcp-services"), strings.HasPrefix(path, "sandbox-configs"), strings.HasPrefix(path, "vector-stores"), strings.HasPrefix(path, "storage-backends"), strings.HasPrefix(path, "web-search-providers"), strings.HasPrefix(path, "datasource"):
// Read-only selectors are used by content/Agent setup as well as settings.
if method == http.MethodGet || method == http.MethodHead {
return ""
}
module = "settings"
}
if module == "" {
return ""
}
action := "read"
if method != http.MethodGet && method != http.MethodHead && !strings.HasSuffix(path, "/search") && !strings.HasSuffix(path, "/hybrid-search") && path != "knowledge-search" {
action = "write"
}
return module + "." + action
}
func (s *Service) Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
t, u := identity(c.Request.Context())
if t == 0 || u == "" || types.IsSyntheticUserID(u) {
c.Next()
return
}
path := c.FullPath()
if strings.Contains(path, "/auth/") || path == "/api/v1/access/me" {
c.Next()
return
}
snapshot, err := s.Snapshot(c.Request.Context())
if err != nil {
fail(c, 503, "权限信息暂不可用,请稍后重试")
return
}
ctx := accesspolicy.WithSnapshot(c.Request.Context(), snapshot)
c.Request = c.Request.WithContext(ctx)
kind := c.Param("kind")
action := c.Param("action")
if strings.Contains(path, "/workbench/jobs/") {
var row struct{ Kind, Action string }
if e := s.db.Table("workbench_jobs").Select("kind, action").Where("tenant_id=? AND owner_id=? AND id=?", t, u, c.Param("id")).Take(&row).Error; e == nil {
kind = row.Kind
action = row.Action
}
}
permission := requiredPermission(c.Request.Method, path, kind, action)
if strings.HasSuffix(path, "/operations/records/:recordKind") {
switch c.Param("recordKind") {
case "transactions":
permission = "balance.read"
case "usage":
permission = "usage.read"
case "audit":
permission = "audit.read"
case "system":
permission = "system.read"
case "consumption":
permission = "knowledge.read"
case "memories":
permission = "agents.read"
}
}
if permission != "" && ((!administrator(ctx) && adminOnlyPermission(permission)) || !accesspolicy.Allowed(ctx, permission)) {
label := permission
for _, m := range Catalog {
for _, p := range m.Permissions {
if p.ID == permission {
label = m.Name + " / " + p.Name
}
}
}
fail(c, 403, "没有权限:"+label)
c.Set("required_permission_label", label)
return
}
c.Next()
}
}