168 lines
6.4 KiB
Go
168 lines
6.4 KiB
Go
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()
|
||
|
|
}
|
||
|
|
}
|