// 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 凭据所属命名空间,不能由请求体覆盖。 NamespaceID core.ID // CredentialID 凭据记录标识。 CredentialID core.ID // Digest 来自原始秘密的摘要,不包含可重放的原始秘密。 Digest core.Digest } 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{"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 }