95 lines
3.3 KiB
Go
95 lines
3.3 KiB
Go
// 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{"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
|
||
}
|