internal/server/group_management_test.go
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: Apache-2.0
package server
import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"strconv"
"testing"
"github.com/X4Applegate/caddyui/internal/auth"
appdb "github.com/X4Applegate/caddyui/internal/db"
"github.com/X4Applegate/caddyui/internal/models"
"github.com/go-chi/chi/v5"
)
// TestGroupPeerCanManageSharedResources is the regression test for review
// finding #7 (v2.52.5): access-groups grant collaborative management. A
// user-role account may manage resources owned by a user who shares a group
// with them, while a user with no shared group still gets 403.
func TestGroupPeerCanManageSharedResources(t *testing.T) {
conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = conn.Close() })
serverID, err := models.CreateCaddyServer(conn, &models.CaddyServer{Name: "primary", AdminURL: "http://primary.invalid:2019"})
if err != nil {
t.Fatal(err)
}
pw, _ := auth.HashPassword("x")
ownerID, err := models.CreateUser(conn, "owner@example.com", pw, "Owner", models.RoleUser)
if err != nil {
t.Fatal(err)
}
peerID, err := models.CreateUser(conn, "peer@example.com", pw, "Peer", models.RoleUser)
if err != nil {
t.Fatal(err)
}
strangerID, err := models.CreateUser(conn, "stranger@example.com", pw, "Stranger", models.RoleUser)
if err != nil {
t.Fatal(err)
}
// owner and peer share a group; stranger is in none.
gid, err := models.CreateGroup(conn, "team-a", "")
if err != nil {
t.Fatal(err)
}
if err := models.SetGroupMembers(conn, gid, []int64{ownerID, peerID}); err != nil {
t.Fatal(err)
}
phID, err := models.CreateProxyHost(conn, serverID, ownerID, &models.ProxyHost{
Domains: "owner.test", ForwardScheme: "http", ForwardHost: "backend", ForwardPort: 8080, Enabled: true,
})
if err != nil {
t.Fatal(err)
}
s := &Server{DB: conn}
owner := &models.User{ID: ownerID, Email: "owner@example.com", Role: models.RoleUser}
peer := &models.User{ID: peerID, Email: "peer@example.com", Role: models.RoleUser}
stranger := &models.User{ID: strangerID, Email: "stranger@example.com", Role: models.RoleUser}
// Unit-level: the single authorization gate.
ph, _ := models.GetProxyHost(conn, phID)
if !s.canManageOwned(owner, ph.OwnerID) {
t.Error("owner should manage their own host")
}
if !s.canManageOwned(peer, ph.OwnerID) {
t.Error("group peer should be able to manage the shared host")
}
if s.canManageOwned(stranger, ph.OwnerID) {
t.Error("a user with no shared group must NOT manage the host")
}
// End-to-end through a REST handler: peer gets 200, stranger 403.
get := func(u *models.User) int {
req := httptest.NewRequest(http.MethodGet, "/api/v1/proxy-hosts/"+strconv.FormatInt(phID, 10), nil)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", strconv.FormatInt(phID, 10))
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
ctx = context.WithValue(ctx, auth.ContextUserKey, u)
rec := httptest.NewRecorder()
s.apiV1GetProxyHost(rec, req.WithContext(ctx))
return rec.Code
}
if code := get(peer); code != http.StatusOK {
t.Errorf("group peer GET: expected 200, got %d", code)
}
if code := get(stranger); code != http.StatusForbidden {
t.Errorf("stranger GET: expected 403, got %d", code)
}
}