refactor(scum): remove legacy projection workflows
This commit is contained in:
@@ -2,10 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
"browser.local/platform/dto"
|
||||
)
|
||||
|
||||
func (h *coreHandlers) serverSCUMPlayers(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -13,12 +9,7 @@ func (h *coreHandlers) serverSCUMPlayers(w http.ResponseWriter, r *http.Request)
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMPlayerLiveStatesForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMPlayerLiveStatesFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMSquads(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -26,12 +17,7 @@ func (h *coreHandlers) serverSCUMSquads(w http.ResponseWriter, r *http.Request)
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMSquadsForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMSquadsFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMSquadMembers(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -39,12 +25,7 @@ func (h *coreHandlers) serverSCUMSquadMembers(w http.ResponseWriter, r *http.Req
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMSquadMembersForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMSquadMembersFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMVehicles(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -52,12 +33,7 @@ func (h *coreHandlers) serverSCUMVehicles(w http.ResponseWriter, r *http.Request
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMVehiclesForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMVehiclesFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMFlags(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -65,12 +41,7 @@ func (h *coreHandlers) serverSCUMFlags(w http.ResponseWriter, r *http.Request) {
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMFlagsForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMFlagsFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMPositions(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -78,12 +49,7 @@ func (h *coreHandlers) serverSCUMPositions(w http.ResponseWriter, r *http.Reques
|
||||
writeMethodNotAllowed(w, http.MethodGet)
|
||||
return
|
||||
}
|
||||
items, err := h.core.ListSCUMCurrentPositionsForSession(bearerToken(r), scumProjectionFilterFromRequest(r, r.PathValue("id")))
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, dto.SCUMCurrentPositionsFromDomain(items))
|
||||
writeRemovedSCUMEndpoint(w)
|
||||
}
|
||||
|
||||
func (h *coreHandlers) serverSCUMOperations(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -123,22 +89,3 @@ func (h *coreHandlers) serverSCUMWorkflowSteps(w http.ResponseWriter, r *http.Re
|
||||
func writeRemovedSCUMEndpoint(w http.ResponseWriter) {
|
||||
writeAPIError(w, http.StatusNotFound, errorCodeNotFound, "legacy SCUM endpoint removed; use the local SCUM management APIs", nil)
|
||||
}
|
||||
|
||||
func scumProjectionFilterFromRequest(r *http.Request, serverID string) domain.SCUMProjectionFilter {
|
||||
query := r.URL.Query()
|
||||
return domain.SCUMProjectionFilter{ServerInstanceID: serverID, GamePlayerID: query.Get("gamePlayerId"), GamePlayerRecordID: query.Get("gamePlayerRecordId"), UserProfileID: query.Get("userProfileId"), SteamID: query.Get("steamId"), SquadID: query.Get("squadId"), VehicleID: query.Get("vehicleId"), FlagID: query.Get("flagId"), SubjectType: domain.SCUMProjectionSubject(query.Get("subjectType")), QueryKey: query.Get("queryKey"), Freshness: domain.SCUMProjectionFreshness(query.Get("freshness")), Search: query.Get("search"), Limit: boundedQueryLimit(query.Get("limit"), 200)}
|
||||
}
|
||||
|
||||
func boundedQueryLimit(raw string, fallback int) int {
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.Atoi(raw)
|
||||
if err != nil || parsed < 1 {
|
||||
return fallback
|
||||
}
|
||||
if parsed > 500 {
|
||||
return 500
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
@@ -1,106 +1,42 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
"browser.local/platform/dto"
|
||||
"browser.local/platform/repo"
|
||||
"browser.local/platform/service"
|
||||
)
|
||||
|
||||
func TestSCUMResourceAPIsExposeLocalRowsAndRemovedLegacyEndpoints(t *testing.T) {
|
||||
func TestLegacySCUMEndpointsReturnNotFoundWithoutDispatchingJobs(t *testing.T) {
|
||||
store := repo.NewMemoryStore()
|
||||
core := service.NewCoreService(store)
|
||||
if _, err := core.CreateUser(domain.User{ID: "scum-api-owner", DisplayName: "SCUM API Owner", Email: "scum-api-owner@example.test", Status: domain.UserStatusActive, Roles: []string{"server-owner"}, PasswordHash: "secret-password"}); err != nil {
|
||||
t.Fatalf("create owner: %v", err)
|
||||
}
|
||||
plugin := validGamePluginRequest().ToDomain()
|
||||
plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.game-client.read")
|
||||
plugin.GameClientBridge.Retention = domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000}
|
||||
if _, err := core.CreateGamePlugin(plugin); err != nil {
|
||||
if _, err := core.CreateGamePlugin(validGamePluginRequest().ToDomain()); err != nil {
|
||||
t.Fatalf("create plugin: %v", err)
|
||||
}
|
||||
endpoint := validRunEndpointRequest().ToDomain()
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery, domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProtectedRCON)
|
||||
endpoint.LastHeartbeatAt = time.Now().UTC()
|
||||
if _, err := core.CreateRunEndpoint(endpoint); err != nil {
|
||||
if _, err := core.CreateRunEndpoint(validRunEndpointRequest().ToDomain()); err != nil {
|
||||
t.Fatalf("create endpoint: %v", err)
|
||||
}
|
||||
if _, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-scum-api", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "SCUM API", OwnerUserID: "scum-api-owner", State: domain.ServerInstanceStateRunning, ConfigVersion: 1}); err != nil {
|
||||
if _, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-scum-api", PluginID: "server.scum", RunEndpointID: "run-local", Name: "SCUM API", OwnerUserID: "scum-api-owner", State: domain.ServerInstanceStateRunning, ConfigVersion: 1}); err != nil {
|
||||
t.Fatalf("create server: %v", err)
|
||||
}
|
||||
if _, err := core.ApplySCUMObservationResult(domain.SCUMObservationResult{ServerInstanceID: "server-scum-api", PluginID: plugin.ID, Source: "run.sqlite.read", QueryKey: "scum.player.profile", Sequence: 1, Checksum: "sha256:api-profile", ObservedAt: time.Now().UTC(), Rows: []map[string]any{{"gamePlayerId": "steam-api", "displayName": "API Player", "normalBalance": 25, "x": 1, "y": 2, "z": 3}}}); err != nil {
|
||||
t.Fatalf("seed projection: %v", err)
|
||||
}
|
||||
auth, err := core.LoginUser(domain.UserLogin{Account: "scum-api-owner@example.test", Password: "secret-password"})
|
||||
if err != nil {
|
||||
t.Fatalf("login: %v", err)
|
||||
}
|
||||
router := NewAuthorizedRouterWithCore(core)
|
||||
players := getJSONWithAuth[dto.SCUMPlayerLiveStateListResponse](t, router, "/api/v1/server-instances/server-scum-api/scum/players", auth.SessionID)
|
||||
if players.Count != 1 || players.Items[0].GamePlayerID != "steam-api" || players.Items[0].Position.X != 1 {
|
||||
t.Fatalf("unexpected SCUM players response: %+v", players)
|
||||
}
|
||||
body, err := json.Marshal(players)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal responses: %v", err)
|
||||
}
|
||||
for _, forbidden := range []string{"#SetFamePoints", "requestText", "SELECT ", "UPDATE ", "SCUM.db", "password", "run token", "hostPath"} {
|
||||
if strings.Contains(strings.ToUpper(string(body)), strings.ToUpper(forbidden)) {
|
||||
t.Fatalf("SCUM safe API leaked %q: %s", forbidden, body)
|
||||
}
|
||||
}
|
||||
for _, legacy := range []struct{ method, path string }{
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/rcon/commands"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/logs/live"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/logs/backfill"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/files/read-snapshot?key=scum-server-log"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/config"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/config/diff"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/config/approve"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/operations"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations/op-1/approve"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflows"},
|
||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/workflows"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflow-steps?workflowId=workflow-1"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/players"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/squads"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/squad-members"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/vehicles"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/flags"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/positions"},
|
||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/operations"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/operations/op-1/approve"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflows"}, {http.MethodPost, "/api/v1/server-instances/server-scum-api/scum/workflows"}, {http.MethodGet, "/api/v1/server-instances/server-scum-api/scum/workflow-steps?workflowId=workflow-1"},
|
||||
} {
|
||||
recorder := requestWithAuth(t, router, legacy.method, legacy.path, `{}`, auth.SessionID)
|
||||
assertStatus(t, recorder, http.StatusNotFound)
|
||||
assertStatus(t, requestWithAuth(t, router, legacy.method, legacy.path, `{}`, auth.SessionID), http.StatusNotFound)
|
||||
}
|
||||
jobs, err := core.ListJobsForSession(auth.SessionID, domain.JobFilter{ServerInstanceID: "server-scum-api"})
|
||||
if err != nil || len(jobs) != 0 {
|
||||
t.Fatalf("removed SCUM endpoints must not dispatch jobs, got jobs=%+v err=%v", jobs, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMAPIsEnforceServerAuthorization(t *testing.T) {
|
||||
store := repo.NewMemoryStore()
|
||||
core := service.NewCoreService(store)
|
||||
if _, err := core.CreateUser(domain.User{ID: "scum-api-owner", DisplayName: "SCUM API Owner", Email: "scum-api-owner-authz@example.test", Status: domain.UserStatusActive, Roles: []string{"server-owner"}, PasswordHash: "secret-password"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := core.CreateUser(domain.User{ID: "scum-api-other", DisplayName: "SCUM API Other", Email: "scum-api-other-authz@example.test", Status: domain.UserStatusActive, Roles: []string{"server-owner"}, PasswordHash: "secret-password"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := core.CreateGamePlugin(validGamePluginRequest().ToDomain()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := core.CreateRunEndpoint(validRunEndpointRequest().ToDomain()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := core.CreateServerInstance(domain.ServerInstance{ID: "server-scum-authz", PluginID: "server.scum", RunEndpointID: "run-local", Name: "SCUM Authz", OwnerUserID: "scum-api-owner", State: domain.ServerInstanceStateRunning}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
auth, err := core.LoginUser(domain.UserLogin{Account: "scum-api-other-authz@example.test", Password: "secret-password"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
router := NewAuthorizedRouterWithCore(core)
|
||||
assertErrorResponse(t, requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/server-scum-authz/scum/players", "", auth.SessionID), http.StatusForbidden, errorCodeForbidden)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user