Files
nezha_domains/cmd/dashboard/controller/server_group_visibility_test.go
T
naibaandcloudcode 76a5e2b261 fix(server-group): hide guest-empty groups from guest listing
listServerGroup returned every group, including ones whose only
servers are HideForGuest=true (or that have zero members), to
unauthenticated callers. The group name itself is then leaked to
guests even though every server it references is hidden from them.

Guest visitors now only see groups that contain at least one guest-
visible server. Authenticated members and admins keep full visibility
(including their own empty groups) so management UI still works.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 04:07:50 +00:00

123 lines
4.1 KiB
Go

package controller
import (
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/i18n"
"github.com/nezhahq/nezha/service/singleton"
)
func setupServerGroupVisibilityFixture(t *testing.T) {
t.Helper()
originalDB := singleton.DB
originalCache := singleton.Cache
originalLoc := singleton.Loc
originalLocalizer := singleton.Localizer
originalServer := singleton.ServerShared
originalUserInfo := singleton.UserInfoMap
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&model.Server{}, &model.ServerGroup{}, &model.ServerGroupServer{}, &model.User{}))
singleton.DB = db
singleton.Loc = time.UTC
singleton.Cache = cache.New(time.Minute, time.Minute)
singleton.Localizer = i18n.NewLocalizer("en_US", "nezha", "translations", i18n.Translations)
singleton.UserLock.Lock()
singleton.UserInfoMap = map[uint64]model.UserInfo{
1: {Role: model.RoleAdmin},
200: {Role: model.RoleMember},
}
singleton.UserLock.Unlock()
require.NoError(t, db.Create(&model.Server{Common: model.Common{ID: 1, UserID: 1}, Name: "public", UUID: "public", HideForGuest: false}).Error)
require.NoError(t, db.Create(&model.Server{Common: model.Common{ID: 2, UserID: 1}, Name: "hidden", UUID: "hidden", HideForGuest: true}).Error)
require.NoError(t, db.Create(&model.ServerGroup{Common: model.Common{ID: 10, UserID: 1}, Name: "Public Group"}).Error)
require.NoError(t, db.Create(&model.ServerGroup{Common: model.Common{ID: 11, UserID: 1}, Name: "Empty Group"}).Error)
require.NoError(t, db.Create(&model.ServerGroupServer{Common: model.Common{UserID: 1}, ServerGroupId: 10, ServerId: 1}).Error)
singleton.ServerShared = singleton.NewServerClass()
t.Cleanup(func() {
singleton.DB = originalDB
singleton.Cache = originalCache
singleton.Loc = originalLoc
singleton.Localizer = originalLocalizer
singleton.ServerShared = originalServer
singleton.UserLock.Lock()
singleton.UserInfoMap = originalUserInfo
singleton.UserLock.Unlock()
})
}
func newServerGroupCtx(viewer *model.User) *gin.Context {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("GET", "/api/v1/server-group", nil)
if viewer != nil {
c.Set(model.CtxKeyAuthorizedUser, viewer)
}
return c
}
func collectGroupNames(items []*model.ServerGroupResponseItem) []string {
names := make([]string, 0, len(items))
for _, it := range items {
names = append(names, it.Group.Name)
}
return names
}
func TestListServerGroupGuestSkipsGroupsWithoutVisibleServers(t *testing.T) {
setupServerGroupVisibilityFixture(t)
items, err := listServerGroup(newServerGroupCtx(nil))
require.NoError(t, err)
names := collectGroupNames(items)
assert.ElementsMatch(t, []string{"Public Group"}, names,
"a group with no guest-visible servers is meaningless to a guest UI and exposing its name leaks the existence of empty/hidden-only groups")
}
func TestListServerGroupAuthenticatedMemberSeesOwnEmptyGroup(t *testing.T) {
setupServerGroupVisibilityFixture(t)
require.NoError(t, singleton.DB.Create(&model.ServerGroup{Common: model.Common{ID: 12, UserID: 200}, Name: "member empty group"}).Error)
items, err := listServerGroup(newServerGroupCtx(&model.User{
Common: model.Common{ID: 200},
Role: model.RoleMember,
}))
require.NoError(t, err)
names := collectGroupNames(items)
assert.Contains(t, names, "member empty group", "owner must still see their own empty group")
}
func TestListServerGroupAdminSeesAllGroupsIncludingEmpty(t *testing.T) {
setupServerGroupVisibilityFixture(t)
items, err := listServerGroup(newServerGroupCtx(&model.User{
Common: model.Common{ID: 1},
Role: model.RoleAdmin,
}))
require.NoError(t, err)
names := collectGroupNames(items)
assert.ElementsMatch(t, []string{"Public Group", "Empty Group"}, names,
"admin must keep full visibility, including empty groups")
}