Ctrl K
ComunidadGuías…

internal/server/group_management_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// 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)
	}
}