83 lines
3.2 KiB
Go
83 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-951 (security review 2026-10-12): GET /customers, /customers/{id} and /customers/{id}/history answered with NO
|
|
// credential and returned every customer's name, dashboard address and stored box report. Since v0.148.0 they are
|
|
// operator/global key only. Red-proof (2026-10-12): with the requireGlobalKey call removed from handleCustomer,
|
|
// TestCustomersRoutes_NoCredentialIsRefused failed (GET /customers/c1 answered 200).
|
|
|
|
const customersTestName = "Teszt Csalad R935"
|
|
|
|
// newCustomersHandler: customer c1 (key ckey) with one stored report and a host h1 (key hkey) under it; a second
|
|
// customer c2 (key c2key).
|
|
func newCustomersHandler(t *testing.T) (*Handler, *store.Store) {
|
|
t.Helper()
|
|
h, st := newTwoCustomerHandler(t)
|
|
if err := st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "hkey"}); err != nil {
|
|
t.Fatalf("UpsertHost: %v", err)
|
|
}
|
|
rr := do(h, http.MethodPost, "/report", "ckey", `{"customer_id":"c1","customer_name":"`+customersTestName+`"}`)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("seed report: status = %d, body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
return h, st
|
|
}
|
|
|
|
var customersRoutes = []string{"/customers", "/customers/c1", "/customers/c1/history"}
|
|
|
|
// Scenario A — a stranger, no Authorization header.
|
|
func TestCustomersRoutes_NoCredentialIsRefused(t *testing.T) {
|
|
h, _ := newCustomersHandler(t)
|
|
for _, p := range customersRoutes {
|
|
rr := do(h, http.MethodGet, p, "", "")
|
|
if rr.Code != http.StatusUnauthorized && rr.Code != http.StatusForbidden {
|
|
t.Errorf("GET %s with no credential: status = %d, want 401/403", p, rr.Code)
|
|
}
|
|
if strings.Contains(rr.Body.String(), customersTestName) || strings.Contains(rr.Body.String(), `"c1"`) {
|
|
t.Errorf("GET %s with no credential: body names a customer: %q", p, rr.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scenario B — a box's own keys (per-customer, per-host, another household's) never list customers.
|
|
func TestCustomersRoutes_BoxKeysAreRefused(t *testing.T) {
|
|
h, _ := newCustomersHandler(t)
|
|
for _, key := range []string{"ckey", "hkey", "c2key", "not-a-key"} {
|
|
for _, p := range customersRoutes {
|
|
if rr := do(h, http.MethodGet, p, key, ""); rr.Code != http.StatusForbidden {
|
|
t.Errorf("GET %s with box key %q: status = %d, want 403", p, key, rr.Code)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scenario C — the operator key gets what v0.147.0 returned to anyone.
|
|
func TestCustomersRoutes_GlobalKeyUnchanged(t *testing.T) {
|
|
h, st := newCustomersHandler(t)
|
|
|
|
rr := do(h, http.MethodGet, "/customers", globalKey, "")
|
|
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), customersTestName) {
|
|
t.Fatalf("GET /customers with global key: status = %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
c, err := st.GetCustomer("c1")
|
|
if err != nil || c == nil {
|
|
t.Fatalf("GetCustomer: %v %v", c, err)
|
|
}
|
|
rr = do(h, http.MethodGet, "/customers/c1", globalKey, "")
|
|
if rr.Code != http.StatusOK || rr.Body.String() != c.ReportJSON {
|
|
t.Fatalf("GET /customers/c1 with global key: status = %d, body differs from the stored report", rr.Code)
|
|
}
|
|
|
|
if rr = do(h, http.MethodGet, "/customers/c1/history", globalKey, ""); rr.Code != http.StatusOK {
|
|
t.Fatalf("GET /customers/c1/history with global key: status = %d", rr.Code)
|
|
}
|
|
}
|