feat: add user status and path permissions

This commit is contained in:
OpenClaw Agent
2026-09-22 01:30:30 +08:00
parent eafc6c6499
commit 71757ec0fa
5 changed files with 163 additions and 16 deletions
+71 -7
View File
@@ -16,9 +16,18 @@ type UserStore struct {
}
type userRecord struct {
Username string `json:"username"`
Password string `json:"passwordHash"`
Home string `json:"home"`
Username string `json:"username"`
Password string `json:"passwordHash"`
Home string `json:"home"`
Enabled *bool `json:"enabled,omitempty"`
Rules []userRule `json:"rules,omitempty"`
}
type userRule struct {
Dir string `json:"dir"`
Mask string `json:"mask"`
Deny bool `json:"deny,omitempty"`
ChildrenOnly bool `json:"childrenOnly,omitempty"`
}
var validUsername = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$")
@@ -30,6 +39,11 @@ func ValidateUsername(username string) error {
return nil
}
func unsafeUserPath(p string) bool {
p = strings.ReplaceAll(p, "\\", "/")
return p == ".." || strings.HasPrefix(p, "../") || strings.Contains(p, "/../") || strings.HasPrefix(p, "/")
}
func (u *UserStore) Load(m *Manager, homeRoot string) error {
if u.Path == "" {
return nil
@@ -56,8 +70,22 @@ func (u *UserStore) Load(m *Manager, homeRoot string) error {
if home == "" {
home = filepath.ToSlash(filepath.Join(homeRoot, rec.Username))
}
a := &Account{Username: rec.Username, PasswordHash: rec.Password, Mask: None, Home: home}
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
enabled := true
if rec.Enabled != nil {
enabled = *rec.Enabled
}
a := &Account{Username: rec.Username, PasswordHash: rec.Password, Mask: None, Home: home, Enabled: enabled}
if len(rec.Rules) == 0 {
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
} else {
for _, rr := range rec.Rules {
dir := strings.Trim(strings.ReplaceAll(rr.Dir, "\\", "/"), "/")
if dir == "" || unsafeUserPath(dir) {
continue
}
a.Rules = append(a.Rules, PathRule{Dir: dir, Mask: ParseMask(rr.Mask), Deny: rr.Deny, ChildrenOnly: rr.ChildrenOnly})
}
}
m.Accounts[rec.Username] = a
}
return nil
@@ -74,7 +102,11 @@ func (u *UserStore) Save(m *Manager) error {
if a.PasswordHash == "" || a.Home == "" || a.Admin {
continue
}
records = append(records, userRecord{Username: a.Username, Password: a.PasswordHash, Home: a.Home})
rec := userRecord{Username: a.Username, Password: a.PasswordHash, Home: a.Home, Enabled: &a.Enabled}
for _, rr := range a.Rules {
rec.Rules = append(rec.Rules, userRule{Dir: rr.Dir, Mask: maskString(rr.Mask), Deny: rr.Deny, ChildrenOnly: rr.ChildrenOnly})
}
records = append(records, rec)
}
data, err := json.MarshalIndent(records, "", " ")
if err != nil {
@@ -103,12 +135,44 @@ func (u *UserStore) Add(m *Manager, username, password, home string) (*Account,
return nil, fmt.Errorf("user already exists")
}
home = strings.Trim(home, "/")
a := &Account{Username: username, PasswordHash: PasswordHash(password), Mask: None, Home: home}
a := &Account{Username: username, PasswordHash: PasswordHash(password), Mask: None, Home: home, Enabled: true}
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
m.Accounts[username] = a
return a, u.Save(m)
}
func maskString(p PermMask) string {
var b strings.Builder
if p.HasRead() {
b.WriteByte('r')
}
if p.HasWrite() {
b.WriteByte('w')
}
if p.HasDelete() {
b.WriteByte('d')
}
return b.String()
}
func (u *UserStore) Update(m *Manager, username, password string, enabled *bool, rules []PathRule) error {
a, ok := m.Accounts[username]
if !ok || a.PasswordHash == "" || a.Admin {
return fmt.Errorf("managed user not found")
}
if strings.TrimSpace(password) != "" {
a.PasswordHash = PasswordHash(password)
a.Password = ""
}
if enabled != nil {
a.Enabled = *enabled
}
if rules != nil {
a.Rules = rules
}
return u.Save(m)
}
func (u *UserStore) UpdatePassword(m *Manager, username, password string) error {
if strings.TrimSpace(password) == "" {
return fmt.Errorf("password required")