// Package auth 定义凭据格式与权限快照;凭据原文通过签发或管理员审计读取接口交付。 package auth import ( "crypto/rand" "crypto/sha256" "encoding/base64" "strings" "git.yuntuo.hk/k2safe/orbit/internal/fault" core "git.yuntuo.hk/k2safe/orbit/model" ) // Token 是已解析但尚未认证的身份,服务端仍须核对数据库摘要与有效期。 type Token struct { // NamespaceID 凭据所属命名空间;仅服务端已核验的 Console 身份可指定管理范围。 NamespaceID core.ID // CredentialID 凭据记录标识。 CredentialID core.ID // Digest 来自原始秘密的摘要,不包含可重放的原始秘密。 Digest core.Digest // Console 仅由服务端控制台桥接器设置,Parse 永远不从外部令牌读取。 Console *ConsoleIdentity } // ConsoleIdentity 保留跨应用管理请求的原始会话身份;每个业务事务必须重新核验。 type ConsoleIdentity struct { SessionNamespaceID core.ID // SessionNamespaceID 登录会话原有范围,不随页面筛选改变。 Username string // Username 部署指定的单管理员账号,不能来自请求参数。 UserID core.ID // UserID 已解析的人类账号;事务中仍核对当前会话归属。 } // ActorID 为业务审计提供实际操作者,不把跨应用操作记成目标应用的 SDK。 func (t Token) ActorID() string { if t.Console != nil { return "console:" + string(t.Console.UserID) } return string(t.CredentialID) } func Issue(namespace, credential core.ID) (string, core.Digest, error) { if err := namespace.Validate(); err != nil { return "", core.Digest{}, err } if err := credential.Validate(); err != nil { return "", core.Digest{}, err } var secret [32]byte if _, err := rand.Read(secret[:]); err != nil { return "", core.Digest{}, err } return "orb1." + string(namespace) + "." + string(credential) + "." + base64.RawURLEncoding.EncodeToString(secret[:]), core.Digest(sha256.Sum256(secret[:])), nil } func Parse(value string) (Token, error) { p := strings.Split(value, ".") if len(p) != 4 || p[0] != "orb1" || len(p[3]) != 43 { return Token{}, fault.Unauthorized } ns, id := core.ID(p[1]), core.ID(p[2]) if ns.Validate() != nil || id.Validate() != nil { return Token{}, fault.Unauthorized } secret, err := base64.RawURLEncoding.Strict().DecodeString(p[3]) if err != nil || len(secret) != 32 { return Token{}, fault.Unauthorized } return Token{NamespaceID: ns, CredentialID: id, Digest: core.Digest(sha256.Sum256(secret))}, nil } // Permissions 是凭据的授权快照;队列动作同时核验队列范围,显式应用动作作用于整个所属应用。 type Permissions struct { // Actions 明确授权的动作集合,首版不使用动作通配符。 Actions []string `json:"actions"` // Queues 可访问的队列 ID;星号表示当前命名空间内全部队列。 Queues []string `json:"queues"` } var supported = map[string]bool{"workflows:audit:read": true, "workflows:recover": true, "workflows:signal": true, "workflows:control": true, "workflows:start": true, "workflows:read": true, "workflows:definitions:read": true, "workflows:definitions:publish": true, "tasks:review": true, "tasks:retry": true, "tasks:audit:read": true, "events:read": true, "events:manage": true, "events:consume": true, "application:resources:read": true, "application:resources:manage": true, "tasks:submit": true, "tasks:read": true, "tasks:cancel": true, "queues:read": true, "queues:manage": true, "workers:connect": true, "workers:consume": true, "schedules:read": true, "schedules:manage": true} func (p Permissions) Validate() error { if len(p.Actions) == 0 || len(p.Actions) > 32 || len(p.Queues) == 0 || len(p.Queues) > 128 { return fault.Invalid("invalid permission scope") } for _, a := range p.Actions { if !supported[a] { return fault.Invalid("unsupported permission action") } } for _, q := range p.Queues { if q != "*" && core.ID(q).Validate() != nil { return fault.Invalid("invalid queue permission") } } return nil } func (p Permissions) Allows(action string) bool { for _, a := range p.Actions { if a == action { return true } } return false } func (p Permissions) AllowsQueue(queue core.ID) bool { for _, q := range p.Queues { if q == "*" || q == string(queue) { return true } } return false }