fix: a group filter value that is not a group id lists nobody (D-23)
User.FilterScope handed the filter value to the database as it came. A value such as abc failed the query, so the Users list answered 500. - groupFilterID reads a positive whole number, as a number or its text - any other value narrows the list to nothing - TestAdminUsersGroupFilterValue fails with a 500 without the change
This commit is contained in:
49
admin_users_edge_test.go
Normal file
49
admin_users_edge_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/sm-user-plugin/models"
|
||||
)
|
||||
|
||||
// TestAdminUsersGroupFilterValue (D-23): the groups filter takes a group id.
|
||||
// A value that is not one matches nobody (or is refused with 422 by the
|
||||
// framework); it must not reach the database as it is, where a non-numeric
|
||||
// text fails the whole list with a 500.
|
||||
func TestAdminUsersGroupFilterValue(t *testing.T) {
|
||||
env := newAdminEnv(t)
|
||||
token := env.admin(t, permAccessUsers)
|
||||
marker := "gfilter" + adminStamp()
|
||||
group := env.seedGroup(t, "Filter "+marker, "")
|
||||
member := env.seedUser(t, marker, nil)
|
||||
other := env.seedUser(t, marker, nil)
|
||||
env.join(t, member, group)
|
||||
|
||||
gid := strconv.FormatUint(uint64(group.ID), 10)
|
||||
if page := env.users(t, token, "search="+marker+"&filter[groups]="+gid); page.Total != 1 || !page.has(member.ID) || page.has(other.ID) {
|
||||
t.Fatalf("group filter = %s", page.Raw)
|
||||
}
|
||||
for _, value := range []string{"abc", "1%20OR%201=1", "-1", "0", "1.5", "99999999999999999999", gid + "x"} {
|
||||
rec := env.call(t, http.MethodGet, adminAPI(adminUsersPath)+"?search="+marker+"&filter[groups]="+value, token, nil)
|
||||
// The framework refuses a value it cannot read as a scope argument
|
||||
// with 422; whatever it lets through must list nobody.
|
||||
if rec.Code == http.StatusUnprocessableEntity {
|
||||
continue
|
||||
}
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("filter[groups]=%s answers %d: %s", value, rec.Code, rec.Body.String())
|
||||
}
|
||||
var rows []map[string]any
|
||||
adminInto(t, rec, &rows)
|
||||
if len(rows) != 0 {
|
||||
t.Fatalf("filter[groups]=%s lists %d users, want none", value, len(rows))
|
||||
}
|
||||
}
|
||||
// The list without the filter is unaffected.
|
||||
if page := env.users(t, token, "search="+marker); page.Total != 2 {
|
||||
t.Fatalf("unfiltered list = %s", page.Raw)
|
||||
}
|
||||
_ = models.FilterByGroup
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
||||
@@ -99,7 +102,7 @@ const FilterByGroup = "filterByGroup"
|
||||
func (User) FilterScopes() []string { return []string{FilterByGroup} }
|
||||
|
||||
// FilterScope narrows db to the members of the group whose id is value. Any
|
||||
// other scope name matches nothing.
|
||||
// other scope name, and a value that is not a group id, match nothing.
|
||||
func (User) FilterScope(name string, db *gorm.DB, value any) *gorm.DB {
|
||||
if db == nil {
|
||||
return db
|
||||
@@ -107,7 +110,26 @@ func (User) FilterScope(name string, db *gorm.DB, value any) *gorm.DB {
|
||||
if name != FilterByGroup {
|
||||
return db.Where("1 = 0")
|
||||
}
|
||||
return db.Where("users.id IN (SELECT user_id FROM users_groups WHERE user_group_id = ?)", value)
|
||||
id, ok := groupFilterID(value)
|
||||
if !ok {
|
||||
return db.Where("1 = 0")
|
||||
}
|
||||
return db.Where("users.id IN (SELECT user_id FROM users_groups WHERE user_group_id = ?)", id)
|
||||
}
|
||||
|
||||
// groupFilterID reads the value of the group filter: a positive whole number,
|
||||
// as a number or as its decimal text. Anything else is not a group id, and
|
||||
// handing it to the database would fail the whole list.
|
||||
func groupFilterID(value any) (uint64, bool) {
|
||||
text := strings.TrimSpace(fmt.Sprint(value))
|
||||
if value == nil || text == "" {
|
||||
return 0, false
|
||||
}
|
||||
id, err := strconv.ParseUint(text, 10, 32)
|
||||
if err != nil || id == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
Reference in New Issue
Block a user