package auth import ( "fmt" "strings" "time" jwt "github.com/golang-jwt/jwt/v4" ) 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 Deny bool ChildrenOnly bool } 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 | Delete} } 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 { return a.maskFor(path, false) } // MaskForEntry returns the permission shown for a directory/file while its // parent directory is being listed. CHFS section rules rule.r/rule.w/rule.d // affect the contents of the named directory, but the directory entry itself // keeps the inherited permission until it is opened. func (a *Account) MaskForEntry(path string) PermMask { return a.maskFor(path, true) } func (a *Account) maskFor(path string, entry bool) PermMask { path = strings.Trim(path, "/") best := -1 mask := a.Mask for _, r := range a.Rules { d := strings.Trim(r.Dir, "/") if !matchPath(d, path) { continue } if entry && r.ChildrenOnly && matchPathExact(d, path) { continue } if len(d) > best { best = len(d) if r.Deny { mask = None } else { 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 !matchSegment(p, ps[i]) { return false } } return true } func matchSegment(pattern, value string) bool { p := []rune(pattern) v := []rune(value) dp := make([][]bool, len(p)+1) for i := range dp { dp[i] = make([]bool, len(v)+1) } dp[0][0] = true for i := 1; i <= len(p); i++ { if p[i-1] == '*' { dp[i][0] = dp[i-1][0] } } for i := 1; i <= len(p); i++ { for j := 1; j <= len(v); j++ { switch p[i-1] { case '*': dp[i][j] = dp[i-1][j] || dp[i][j-1] case '?': dp[i][j] = dp[i-1][j-1] default: dp[i][j] = dp[i-1][j-1] && p[i-1] == v[j-1] } } } return dp[len(p)][len(v)] } func matchPathExact(pattern, path string) bool { pp := strings.Split(strings.Trim(pattern, "/"), "/") ps := strings.Split(strings.Trim(path, "/"), "/") if len(pp) != len(ps) { return false } return matchPath(pattern, path) } func MatchPath(pattern, path string) bool { return matchPath(pattern, path) } const jwtSecret = "_pengyichen2015_" type jwtClaims struct { Acc string `json:"acc"` jwt.StandardClaims } func NewToken(username string, timeoutMinutes int) (string, error) { if timeoutMinutes <= 0 { timeoutMinutes = 30 } claims := jwtClaims{Acc: username, StandardClaims: jwt.StandardClaims{ExpiresAt: time.Now().Add(time.Duration(timeoutMinutes) * time.Minute).Unix()}} token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(jwtSecret)) } func GetAccFromToken(raw string) (string, error) { if raw == "" { return "", fmt.Errorf("empty token") } var claims jwtClaims token, err := jwt.ParseWithClaims(raw, &claims, func(t *jwt.Token) (interface{}, error) { if t.Method != jwt.SigningMethodHS256 { return nil, fmt.Errorf("unexpected signing method: %s", t.Method.Alg()) } return []byte(jwtSecret), nil }) if err != nil || token == nil || !token.Valid { if err == nil { err = fmt.Errorf("invalid token") } return "", err } if claims.Acc == "" { return "", fmt.Errorf("token missing account") } return claims.Acc, nil }