3x-ui/web/controller/access_control_test.go

156 lines
3.9 KiB
Go
Raw Normal View History

2026-04-06 14:12:38 +00:00
package controller
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/web/global"
"github.com/mhsanaei/3x-ui/v2/web/session"
"github.com/mhsanaei/3x-ui/v2/xray"
"github.com/robfig/cron/v3"
)
type testWebServer struct {
cron *cron.Cron
}
func (s *testWebServer) GetCron() *cron.Cron { return s.cron }
func (s *testWebServer) GetCtx() context.Context { return context.Background() }
func (s *testWebServer) GetWSHub() any { return nil }
func setupControllerTestDB(t *testing.T) {
t.Helper()
tmpDir := t.TempDir()
t.Setenv("XUI_DEBUG", "")
t.Setenv("XUI_DB_FOLDER", tmpDir)
dbPath := filepath.Join(tmpDir, "controller-test.db")
if err := database.InitDBWithPath(dbPath); err != nil {
t.Fatalf("InitDB failed: %v", err)
}
t.Cleanup(func() {
database.CloseDB()
})
}
func newTestRouter(t *testing.T) *gin.Engine {
t.Helper()
gin.SetMode(gin.TestMode)
r := gin.New()
store := cookie.NewStore([]byte("test-secret"))
r.Use(sessions.Sessions("3x-ui", store))
r.Use(func(c *gin.Context) {
c.Set("base_path", "/")
role := c.GetHeader("X-Test-Role")
if role == "" {
return
}
user := &model.User{
Id: 1,
Username: c.GetHeader("X-Test-Username"),
Role: role,
}
if user.Username == "" {
user.Username = "tester@example.com"
}
session.SetLoginUser(c, user)
})
return r
}
func TestXUIController_SettingsPageRequiresAdmin(t *testing.T) {
r := newTestRouter(t)
NewXUIController(r.Group("/"))
req := httptest.NewRequest(http.MethodGet, "/panel/settings", nil)
req.Header.Set("X-Test-Role", "user")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusTemporaryRedirect {
t.Fatalf("expected %d, got %d", http.StatusTemporaryRedirect, w.Code)
}
if got := w.Header().Get("Location"); got != "/panel/user" {
t.Fatalf("expected redirect to /panel/user, got %q", got)
}
}
func TestAPIController_AdminEndpointsRequireAdmin(t *testing.T) {
global.SetWebServer(&testWebServer{cron: cron.New()})
r := newTestRouter(t)
NewAPIController(r.Group("/"))
for _, path := range []string{
"/panel/api/inbounds/list",
"/panel/api/server/status",
} {
req := httptest.NewRequest(http.MethodGet, path, nil)
req.Header.Set("X-Test-Role", "user")
req.Header.Set("X-Requested-With", "XMLHttpRequest")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusForbidden {
t.Fatalf("%s: expected %d, got %d", path, http.StatusForbidden, w.Code)
}
}
}
func TestAPIController_UserInfoRemainsAvailableToLoggedInUser(t *testing.T) {
setupControllerTestDB(t)
global.SetWebServer(&testWebServer{cron: cron.New()})
inboundSettings, err := json.Marshal(map[string]any{
"clients": []map[string]any{
{"id": "client-1", "email": "tester@example.com", "enable": true, "subId": "sub-1"},
},
})
if err != nil {
t.Fatalf("marshal inbound settings failed: %v", err)
}
inbound := &model.Inbound{
UserId: 1,
Port: 12001,
Protocol: model.VLESS,
Tag: "controller-user-info",
Settings: string(inboundSettings),
}
if err := database.GetDB().Create(inbound).Error; err != nil {
t.Fatalf("create inbound failed: %v", err)
}
if err := database.GetDB().Create(&xray.ClientTraffic{
InboundId: inbound.Id,
Email: "tester@example.com",
Enable: true,
}).Error; err != nil {
t.Fatalf("create client traffic failed: %v", err)
}
r := newTestRouter(t)
NewAPIController(r.Group("/"))
req := httptest.NewRequest(http.MethodGet, "/panel/api/inbounds/userInfo", nil)
req.Header.Set("X-Test-Role", "user")
req.Header.Set("X-Test-Username", "tester@example.com")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected %d, got %d", http.StatusOK, w.Code)
}
}