diff --git a/group/description.go b/group/description.go index 14643f7..0ecb20e 100644 --- a/group/description.go +++ b/group/description.go @@ -602,31 +602,49 @@ func GetUsers(group string) ([]string, string, error) { return users, makeETag(desc.fileSize, desc.modTime), nil } -func GetSanitisedUser(group, username string) (UserDescription, string, error) { +func GetSanitisedUser(group, username string, wildcard bool) (UserDescription, string, error) { + if wildcard && username != "" { + return UserDescription{}, "", + errors.New("wildcard with username") + } + desc, err := GetDescription(group) if err != nil { return UserDescription{}, "", err } - if desc.Users == nil { - return UserDescription{}, "", os.ErrNotExist - } + var u UserDescription + if wildcard { + if desc.WildcardUser == nil { + return UserDescription{}, "", os.ErrNotExist + } + u = *desc.WildcardUser + } else { + if desc.Users == nil { + return UserDescription{}, "", os.ErrNotExist + } - u, ok := desc.Users[username] - if !ok { - return UserDescription{}, "", os.ErrNotExist + ok := false + u, ok = desc.Users[username] + if !ok { + return UserDescription{}, "", os.ErrNotExist + } } u.Password = Password{} return u, makeETag(desc.fileSize, desc.modTime), nil } -func GetUserTag(group, username string) (string, error) { - _, etag, err := GetSanitisedUser(group, username) +func GetUserTag(group, username string, wildcard bool) (string, error) { + _, etag, err := GetSanitisedUser(group, username, wildcard) return etag, err } -func DeleteUser(group, username, etag string) error { +func DeleteUser(group, username string, wildcard bool, etag string) error { + if wildcard && username != "" { + return errors.New("wildcard with username") + } + groups.mu.Lock() defer groups.mu.Unlock() @@ -634,13 +652,19 @@ func DeleteUser(group, username, etag string) error { if err != nil { return err } - if desc.Users == nil { - return os.ErrNotExist - } - _, ok := desc.Users[username] - if !ok { - return os.ErrNotExist + if wildcard { + if desc.WildcardUser == nil { + return os.ErrNotExist + } + } else { + if desc.Users == nil { + return os.ErrNotExist + } + _, ok := desc.Users[username] + if !ok { + return os.ErrNotExist + } } oldetag := makeETag(desc.fileSize, desc.modTime) @@ -648,14 +672,23 @@ func DeleteUser(group, username, etag string) error { return ErrTagMismatch } - delete(desc.Users, username) + if wildcard { + desc.WildcardUser = nil + } else { + delete(desc.Users, username) + } + return rewriteDescriptionFile(desc.FileName, desc) } -func UpdateUser(group, username, etag string, user *UserDescription) error { +func UpdateUser(group, username string, wildcard bool, etag string, user *UserDescription) error { + if wildcard && username != "" { + return errors.New("wildcard with username") + } if user.Password.Type != "" || user.Password.Key != nil { return errors.New("user description is not sanitised") } + groups.mu.Lock() defer groups.mu.Unlock() @@ -663,11 +696,21 @@ func UpdateUser(group, username, etag string, user *UserDescription) error { if err != nil { return err } - if desc.Users == nil { - desc.Users = make(map[string]UserDescription) + + var old UserDescription + var ok bool + if wildcard { + if desc.WildcardUser != nil { + ok = true + old = *desc.WildcardUser + } + } else { + if desc.Users == nil { + desc.Users = make(map[string]UserDescription) + } + old, ok = desc.Users[username] } - old, ok := desc.Users[username] var oldetag string if ok { oldetag = makeETag(desc.fileSize, desc.modTime) @@ -681,11 +724,20 @@ func UpdateUser(group, username, etag string, user *UserDescription) error { newuser := *user newuser.Password = old.Password - desc.Users[username] = newuser + + if wildcard { + desc.WildcardUser = &newuser + } else { + desc.Users[username] = newuser + } return rewriteDescriptionFile(desc.FileName, desc) } -func SetUserPassword(group, username string, pw Password) error { +func SetUserPassword(group, username string, wildcard bool, pw Password) error { + if wildcard && username != "" { + return errors.New("wildcard with username") + } + groups.mu.Lock() defer groups.mu.Unlock() @@ -697,12 +749,19 @@ func SetUserPassword(group, username string, pw Password) error { return os.ErrNotExist } - user, ok := desc.Users[username] - if !ok { - return os.ErrNotExist - } + if wildcard { + if desc.WildcardUser == nil { + return os.ErrNotExist + } + desc.WildcardUser.Password = pw + } else { + user, ok := desc.Users[username] + if !ok { + return os.ErrNotExist + } - user.Password = pw - desc.Users[username] = user + user.Password = pw + desc.Users[username] = user + } return rewriteDescriptionFile(desc.FileName, desc) } diff --git a/group/description_test.go b/group/description_test.go index 06fbb5c..4f7e0f6 100644 --- a/group/description_test.go +++ b/group/description_test.go @@ -255,27 +255,31 @@ func TestWritableGroups(t *testing.T) { nil, "Test", err, desc.AllowAnonymous, ) } + testUser(t, "jch", false) + testUser(t, "", true) +} - _, _, err = GetSanitisedUser("test", "jch") +func testUser(t *testing.T, username string, wildcard bool) { + _, _, err := GetSanitisedUser("test", username, wildcard) if !errors.Is(err, os.ErrNotExist) { t.Errorf("GetSanitisedUser: got %v, expected ErrNotExist", err) } - err = UpdateUser("test", "jch", "", &UserDescription{ + err = UpdateUser("test", username, wildcard, "", &UserDescription{ Permissions: Permissions{name: "observe"}, }) if err != nil { t.Errorf("UpdateUser: got %v", err) } - user, token, err := GetSanitisedUser("test", "jch") + user, token, err := GetSanitisedUser("test", username, wildcard) if err != nil || token == "" || user.Permissions.name != "observe" { t.Errorf("GetDescription: got %v %v, expected %v %v", err, user.Permissions.name, nil, "observe", ) } - err = UpdateUser("test", "jch", "", &UserDescription{ + err = UpdateUser("test", username, wildcard, "", &UserDescription{ Permissions: Permissions{name: "present"}, }) if !errors.Is(err, ErrTagMismatch) { @@ -283,7 +287,7 @@ func TestWritableGroups(t *testing.T) { err) } - err = UpdateUser("test", "jch", token, &UserDescription{ + err = UpdateUser("test", username, wildcard, token, &UserDescription{ Permissions: Permissions{name: "present"}, }) if err != nil { @@ -291,20 +295,13 @@ func TestWritableGroups(t *testing.T) { } pw := "pw" - err = SetUserPassword("test", "jch", Password{ + err = SetUserPassword("test", username, wildcard, Password{ Type: "plain", Key: &pw, }) if err != nil { t.Errorf("SetUserPassword: got %v", err) } - - desc, err = GetDescription("test") - if err != nil || *desc.Users["jch"].Password.Key != "pw" { - t.Errorf("GetDescription: got %v %v, expected %v %v", - err, desc.Users["jch"].Password.Key, nil, "pw", - ) - } } func TestSubGroup(t *testing.T) { diff --git a/webserver/api.go b/webserver/api.go index d61009b..e7bcd48 100644 --- a/webserver/api.go +++ b/webserver/api.go @@ -297,7 +297,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string) { } if r.Method == "HEAD" || r.Method == "GET" { - user, etag, err := group.GetSanitisedUser(g, user) + user, etag, err := group.GetSanitisedUser(g, user, false) if err != nil { httpError(w, err) return @@ -310,7 +310,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string) { sendJSON(w, r, user) return } else if r.Method == "PUT" { - etag, err := group.GetUserTag(g, user) + etag, err := group.GetUserTag(g, user, false) if errors.Is(err, os.ErrNotExist) { etag = "" err = nil @@ -329,7 +329,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string) { if done { return } - err = group.UpdateUser(g, user, etag, &newdesc) + err = group.UpdateUser(g, user, false, etag, &newdesc) if err != nil { httpError(w, err) return @@ -341,7 +341,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string) { } return } else if r.Method == "DELETE" { - etag, err := group.GetUserTag(g, user) + etag, err := group.GetUserTag(g, user, false) if err != nil { httpError(w, err) return @@ -352,7 +352,7 @@ func userHandler(w http.ResponseWriter, r *http.Request, g, user string) { return } - err = group.DeleteUser(g, user, etag) + err = group.DeleteUser(g, user, false, etag) if err != nil { httpError(w, err) return @@ -375,7 +375,7 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string) { if done { return } - err := group.SetUserPassword(g, user, pw) + err := group.SetUserPassword(g, user, false, pw) if err != nil { httpError(w, err) return @@ -403,7 +403,7 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string) { Salt: hex.EncodeToString(salt), Iterations: iterations, } - err = group.SetUserPassword(g, user, pw) + err = group.SetUserPassword(g, user, false, pw) if err != nil { httpError(w, err) return @@ -411,7 +411,7 @@ func passwordHandler(w http.ResponseWriter, r *http.Request, g, user string) { w.WriteHeader(http.StatusNoContent) return } else if r.Method == "DELETE" { - err := group.SetUserPassword(g, user, group.Password{}) + err := group.SetUserPassword(g, user, false, group.Password{}) if err != nil { httpError(w, err) return