Files
nezha_domains/cmd/dashboard/controller/service_visibility_test.go
T
naibaandcloudcode a8f014838c fix(service): hide EnableShowInService=false services from sideband endpoints
GHSA-vrmh-5mmx-hjwx: GET /api/v1/server/:id/service and
GET /api/v1/service/:id/history both iterate the raw service list and
emit ServiceName / timing for any service that happens to monitor the
queried server, ignoring the owner's EnableShowInService=false flag.
Both routes are on optionalAuth, so unauthenticated visitors could
enumerate hidden services by name and timing.

Introduce userCanViewService(c, service):
- EnableShowInService=true     -> always visible
- admin                        -> always visible
- authenticated owner          -> visible (HasPermission)
- everyone else                -> hidden

getServiceHistory rejects unknown-or-invisible service with the same
'service not found' message so the endpoint cannot be used as an
oracle. listServerServices pre-filters the sorted service list.

Owners and admins keep their existing visibility into their own hidden
services.

Tests:
- TestUserCanViewServiceVisibleServiceIsPublic
- TestUserCanViewServiceHiddenServiceRejectsGuest
- TestUserCanViewServiceHiddenServiceRejectsForeignMember
- TestUserCanViewServiceHiddenServiceAllowsOwner
- TestUserCanViewServiceHiddenServiceAllowsAdmin

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 03:59:59 +00:00

49 lines
2.1 KiB
Go

package controller
import (
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/nezhahq/nezha/model"
)
func newServiceVisibilityCtx(viewer *model.User) *gin.Context {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
if viewer != nil {
c.Set(model.CtxKeyAuthorizedUser, viewer)
}
return c
}
func TestUserCanViewServiceVisibleServiceIsPublic(t *testing.T) {
visible := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: true}
assert.True(t, userCanViewService(newServiceVisibilityCtx(nil), visible), "guest must see EnableShowInService=true regardless of owner")
}
func TestUserCanViewServiceHiddenServiceRejectsGuest(t *testing.T) {
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
assert.False(t, userCanViewService(newServiceVisibilityCtx(nil), hidden), "guest must NOT see hidden service via per-server / per-id sideband endpoints")
}
func TestUserCanViewServiceHiddenServiceRejectsForeignMember(t *testing.T) {
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
foreign := &model.User{Common: model.Common{ID: 200}, Role: model.RoleMember}
assert.False(t, userCanViewService(newServiceVisibilityCtx(foreign), hidden), "foreign member must NOT see another user's hidden service")
}
func TestUserCanViewServiceHiddenServiceAllowsOwner(t *testing.T) {
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
owner := &model.User{Common: model.Common{ID: 100}, Role: model.RoleMember}
assert.True(t, userCanViewService(newServiceVisibilityCtx(owner), hidden), "owner must still see their own hidden service")
}
func TestUserCanViewServiceHiddenServiceAllowsAdmin(t *testing.T) {
hidden := &model.Service{Common: model.Common{ID: 1, UserID: 100}, EnableShowInService: false}
admin := &model.User{Common: model.Common{ID: 1}, Role: model.RoleAdmin}
assert.True(t, userCanViewService(newServiceVisibilityCtx(admin), hidden), "admin must be able to see any hidden service")
}