fix(service): filter service stats based on user permissions

This commit is contained in:
hamster1963
2026-06-20 00:45:53 +08:00
parent 3d74cd9431
commit 7a61da098b
3 changed files with 45 additions and 20 deletions
@@ -268,7 +268,10 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) {
serviceSentinel, err := singleton.NewServiceSentinel(make(chan *model.Service, 2)) serviceSentinel, err := singleton.NewServiceSentinel(make(chan *model.Service, 2))
assert.NoError(t, err) assert.NoError(t, err)
singleton.ServiceSentinelShared = serviceSentinel singleton.ServiceSentinelShared = serviceSentinel
t.Cleanup(func() { singleton.ServiceSentinelShared = originalServiceSentinel }) t.Cleanup(func() {
serviceSentinel.Close()
singleton.ServiceSentinelShared = originalServiceSentinel
})
singleton.AlertsLock.Lock() singleton.AlertsLock.Lock()
originalCycleTransferStats := singleton.AlertsCycleTransferStatsStore originalCycleTransferStats := singleton.AlertsCycleTransferStatsStore
@@ -290,20 +293,24 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
viewer *model.User viewer *model.User
wantServices []uint64
wantNames map[uint64]string wantNames map[uint64]string
}{ }{
{ {
name: "guest sees public servers only", name: "guest sees public servers only",
wantServices: []uint64{10},
wantNames: map[uint64]string{1: "public server"}, wantNames: map[uint64]string{1: "public server"},
}, },
{ {
name: "member sees public and owned hidden servers", name: "member sees public and owned hidden servers",
viewer: &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember}, viewer: &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember},
wantServices: []uint64{10},
wantNames: map[uint64]string{1: "public server", 3: "hidden member server"}, wantNames: map[uint64]string{1: "public server", 3: "hidden member server"},
}, },
{ {
name: "admin sees every server", name: "admin sees every server",
viewer: &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin}, viewer: &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin},
wantServices: []uint64{10, 11},
wantNames: map[uint64]string{1: "public server", 2: "hidden admin server", 3: "hidden member server"}, wantNames: map[uint64]string{1: "public server", 2: "hidden admin server", 3: "hidden member server"},
}, },
} }
@@ -317,8 +324,7 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) {
got, err := showService(ctx) got, err := showService(ctx)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, got.Services, uint64(10)) assert.ElementsMatch(t, tc.wantServices, serviceResponseIDs(got.Services))
assert.NotContains(t, got.Services, uint64(11))
if assert.Contains(t, got.CycleTransferStats, uint64(7)) { if assert.Contains(t, got.CycleTransferStats, uint64(7)) {
cycleStats := got.CycleTransferStats[7] cycleStats := got.CycleTransferStats[7]
assert.Equal(t, tc.wantNames, cycleStats.ServerName) assert.Equal(t, tc.wantNames, cycleStats.ServerName)
@@ -335,6 +341,14 @@ func TestShowServiceFiltersCycleTransferStatsLikeServerList(t *testing.T) {
} }
} }
func serviceResponseIDs(stats map[uint64]model.ServiceResponseItem) []uint64 {
ids := make([]uint64, 0, len(stats))
for id := range stats {
ids = append(ids, id)
}
return ids
}
func decodeIDs[T ~uint64](t *testing.T, body []byte) []T { func decodeIDs[T ~uint64](t *testing.T, body []byte) []T {
t.Helper() t.Helper()
var resp struct { var resp struct {
+17 -1
View File
@@ -29,7 +29,7 @@ func showService(c *gin.Context) (*model.ServiceResponse, error) {
res, err, _ := requestGroup.Do(serviceResponseCacheKey(c), func() (any, error) { res, err, _ := requestGroup.Do(serviceResponseCacheKey(c), func() (any, error) {
singleton.AlertsLock.RLock() singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock() defer singleton.AlertsLock.RUnlock()
stats := singleton.ServiceSentinelShared.CopyStats() stats := filterServiceStatsForViewer(c, singleton.ServiceSentinelShared.CopyStats())
var cycleTransferStats map[uint64]model.CycleTransferStats var cycleTransferStats map[uint64]model.CycleTransferStats
copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore) copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore)
return []any{ return []any{
@@ -46,6 +46,22 @@ func showService(c *gin.Context) (*model.ServiceResponse, error) {
}, nil }, nil
} }
func filterServiceStatsForViewer(c *gin.Context, stats map[uint64]model.ServiceResponseItem) map[uint64]model.ServiceResponseItem {
if len(stats) == 0 {
return stats
}
services := singleton.ServiceSentinelShared.GetList()
filteredStats := make(map[uint64]model.ServiceResponseItem, len(stats))
for serviceID, stat := range stats {
service, ok := services[serviceID]
if !ok || !userCanViewService(c, service) {
continue
}
filteredStats[serviceID] = stat
}
return filteredStats
}
func serviceResponseCacheKey(c *gin.Context) string { func serviceResponseCacheKey(c *gin.Context) string {
auth, ok := c.Get(model.CtxKeyAuthorizedUser) auth, ok := c.Get(model.CtxKeyAuthorizedUser)
if !ok { if !ok {
-5
View File
@@ -438,11 +438,6 @@ func (ss *ServiceSentinel) CopyStats() map[uint64]model.ServiceResponseItem {
sri := make(map[uint64]model.ServiceResponseItem) sri := make(map[uint64]model.ServiceResponseItem)
for k, service := range stats { for k, service := range stats {
if service.service.HideForGuest {
delete(stats, k)
continue
}
service.ServiceName = service.service.Name service.ServiceName = service.service.Name
sri[k] = service.ServiceResponseItem sri[k] = service.ServiceResponseItem
} }