Files
CHFS-NG/chfs-ng/internal/auth/auth.go
T

135 lines
2.5 KiB
Go

package auth
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"strings"
)
type PermMask uint8
const (
None PermMask = 0
Read PermMask = 1 << iota
Write
Delete
)
func (p PermMask) HasRead() bool { return p&Read != 0 }
func (p PermMask) HasWrite() bool { return p&Write != 0 }
func (p PermMask) HasDelete() bool { return p&Delete != 0 }
func ParseMask(s string) PermMask {
var p PermMask
for _, r := range strings.ToLower(s) {
switch r {
case 'r':
p |= Read
case 'w':
p |= Read | Write
case 'd':
p |= Read | Write | Delete
}
}
return p
}
type PathRule struct {
Dir string
Mask PermMask
}
type Account struct {
Username, Password string
Mask PermMask
Rules []PathRule
}
type Manager struct {
Accounts map[string]*Account
Guest *Account
}
func ParseRules(values []string) (*Manager, error) {
m := &Manager{Accounts: map[string]*Account{}}
for _, value := range values {
for _, item := range strings.Split(value, "|") {
if item == "" {
continue
}
fields := strings.Split(item, ":")
if len(fields) < 3 || len(fields)%2 == 0 {
continue
}
a := &Account{Username: fields[0], Password: fields[1], Mask: ParseMask(fields[2])}
for i := 3; i+1 < len(fields); i += 2 {
a.Rules = append(a.Rules, PathRule{Dir: strings.Trim(fields[i], "/"), Mask: ParseMask(fields[i+1])})
}
if a.Username == "" {
m.Guest = a
} else {
m.Accounts[a.Username] = a
}
}
}
if m.Guest == nil {
m.Guest = &Account{Mask: Read | Write}
}
return m, nil
}
func (m *Manager) Authenticate(user, pwd string) *Account {
if a, ok := m.Accounts[user]; ok && a.Password == pwd {
return a
}
if user == "" && pwd == "" {
return m.Guest
}
return nil
}
func (a *Account) MaskFor(path string) PermMask {
path = strings.Trim(path, "/")
best := -1
mask := a.Mask
for _, r := range a.Rules {
d := strings.Trim(r.Dir, "/")
if matchPath(d, path) && len(d) > best {
best = len(d)
mask = r.Mask
}
}
return mask
}
func matchPath(pattern, path string) bool {
if pattern == "" {
return path == ""
}
pp := strings.Split(pattern, "/")
ps := strings.Split(path, "/")
if len(ps) < len(pp) {
return false
}
for i, p := range pp {
if p == "*" {
return true
}
if p == "?" {
if ps[i] == "" {
return false
}
continue
}
if p != ps[i] {
return false
}
}
return true
}
func NewToken() string {
b := make([]byte, 32)
if _, e := rand.Read(b); e != nil {
h := sha256.Sum256([]byte("fallback"))
return hex.EncodeToString(h[:])
}
return hex.EncodeToString(b)
}