feat: build vehicle data platform and production pipeline
This commit is contained in:
135
vehicle-data-platform/apps/api/internal/app/auth_test.go
Normal file
135
vehicle-data-platform/apps/api/internal/app/auth_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"lingniu/vehicle-data-platform/apps/api/internal/config"
|
||||
"lingniu/vehicle-data-platform/apps/api/internal/platform"
|
||||
)
|
||||
|
||||
const (
|
||||
viewerToken = "viewer-token-at-least-16"
|
||||
operatorToken = "operator-token-at-least-16"
|
||||
adminToken = "admin-token-at-least-16"
|
||||
)
|
||||
|
||||
func testAuthConfig() config.Config {
|
||||
return config.Config{
|
||||
AuthMode: "enforce",
|
||||
AuthTokensJSON: `[
|
||||
{"token":"` + viewerToken + `","name":"viewer-a","role":"viewer"},
|
||||
{"token":"` + operatorToken + `","name":"operator-a","role":"operator"},
|
||||
{"token":"` + adminToken + `","name":"admin-a","role":"admin"}
|
||||
]`,
|
||||
}
|
||||
}
|
||||
|
||||
func authRequest(t *testing.T, cfg config.Config, method, path, token string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := platform.PrincipalFromContext(r.Context())
|
||||
if !ok {
|
||||
t.Fatal("authenticated request reached handler without principal")
|
||||
}
|
||||
w.Header().Set("X-Principal", principal.Name+":"+principal.Role)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
req := httptest.NewRequest(method, path, nil)
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
withAPIAuth(next, cfg).ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
func TestAPIAuthEnforcesTokensAndRoleBoundaries(t *testing.T) {
|
||||
cfg := testAuthConfig()
|
||||
|
||||
missing := authRequest(t, cfg, http.MethodGet, "/api/v2/alerts/rules", "")
|
||||
if missing.Code != http.StatusUnauthorized || !strings.HasPrefix(missing.Header().Get("WWW-Authenticate"), "Bearer") {
|
||||
t.Fatalf("missing token status=%d headers=%v body=%s", missing.Code, missing.Header(), missing.Body.String())
|
||||
}
|
||||
invalid := authRequest(t, cfg, http.MethodGet, "/api/v2/alerts/rules", "wrong-token-at-least-16")
|
||||
if invalid.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("invalid token status=%d body=%s", invalid.Code, invalid.Body.String())
|
||||
}
|
||||
|
||||
viewerQuery := authRequest(t, cfg, http.MethodPost, "/api/v2/alerts/events", viewerToken)
|
||||
if viewerQuery.Code != http.StatusNoContent || viewerQuery.Header().Get("X-Principal") != "viewer-a:viewer" {
|
||||
t.Fatalf("viewer query status=%d principal=%s", viewerQuery.Code, viewerQuery.Header().Get("X-Principal"))
|
||||
}
|
||||
viewerAction := authRequest(t, cfg, http.MethodPost, "/api/v2/alerts/events/a/actions", viewerToken)
|
||||
if viewerAction.Code != http.StatusForbidden {
|
||||
t.Fatalf("viewer mutation should be forbidden, status=%d", viewerAction.Code)
|
||||
}
|
||||
viewerExports := authRequest(t, cfg, http.MethodGet, "/api/v2/exports", viewerToken)
|
||||
if viewerExports.Code != http.StatusForbidden {
|
||||
t.Fatalf("viewer export listing should be forbidden, status=%d", viewerExports.Code)
|
||||
}
|
||||
operatorExports := authRequest(t, cfg, http.MethodGet, "/api/v2/exports/exp_1/download", operatorToken)
|
||||
if operatorExports.Code != http.StatusNoContent {
|
||||
t.Fatalf("operator export download status=%d", operatorExports.Code)
|
||||
}
|
||||
operatorAction := authRequest(t, cfg, http.MethodPost, "/api/v2/alerts/events/a/actions", operatorToken)
|
||||
if operatorAction.Code != http.StatusNoContent {
|
||||
t.Fatalf("operator action status=%d body=%s", operatorAction.Code, operatorAction.Body.String())
|
||||
}
|
||||
operatorRule := authRequest(t, cfg, http.MethodPost, "/api/v2/alerts/rules", operatorToken)
|
||||
if operatorRule.Code != http.StatusForbidden {
|
||||
t.Fatalf("operator rule mutation should be forbidden, status=%d", operatorRule.Code)
|
||||
}
|
||||
operatorProfile := authRequest(t, cfg, http.MethodPut, "/api/v2/vehicles/VIN001/profile", operatorToken)
|
||||
if operatorProfile.Code != http.StatusForbidden {
|
||||
t.Fatalf("operator profile mutation should be forbidden, status=%d", operatorProfile.Code)
|
||||
}
|
||||
operatorProfileSync := authRequest(t, cfg, http.MethodPost, "/api/v2/vehicle-profiles/sync", operatorToken)
|
||||
if operatorProfileSync.Code != http.StatusForbidden {
|
||||
t.Fatalf("operator profile sync should be forbidden, status=%d", operatorProfileSync.Code)
|
||||
}
|
||||
adminProfile := authRequest(t, cfg, http.MethodPut, "/api/v2/vehicles/VIN001/profile", adminToken)
|
||||
if adminProfile.Code != http.StatusNoContent || adminProfile.Header().Get("X-Principal") != "admin-a:admin" {
|
||||
t.Fatalf("admin profile mutation status=%d principal=%s", adminProfile.Code, adminProfile.Header().Get("X-Principal"))
|
||||
}
|
||||
adminProfileSync := authRequest(t, cfg, http.MethodPost, "/api/v2/vehicle-profiles/sync", adminToken)
|
||||
if adminProfileSync.Code != http.StatusNoContent || adminProfileSync.Header().Get("X-Principal") != "admin-a:admin" {
|
||||
t.Fatalf("admin profile sync status=%d principal=%s", adminProfileSync.Code, adminProfileSync.Header().Get("X-Principal"))
|
||||
}
|
||||
adminThreshold := authRequest(t, cfg, http.MethodPut, "/api/v2/access/thresholds", adminToken)
|
||||
if adminThreshold.Code != http.StatusNoContent {
|
||||
t.Fatalf("admin threshold status=%d body=%s", adminThreshold.Code, adminThreshold.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIAuthSessionAndDisabledMode(t *testing.T) {
|
||||
rec := authRequest(t, config.Config{AuthMode: "disabled"}, http.MethodGet, "/api/v2/alerts/rules", "")
|
||||
if rec.Code != http.StatusNoContent || rec.Header().Get("X-Principal") != "local-developer:admin" {
|
||||
t.Fatalf("disabled mode status=%d principal=%s", rec.Code, rec.Header().Get("X-Principal"))
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v2/session", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+operatorToken)
|
||||
session := httptest.NewRecorder()
|
||||
withAPIAuth(http.NotFoundHandler(), testAuthConfig()).ServeHTTP(session, req)
|
||||
if session.Code != http.StatusOK || !strings.Contains(session.Body.String(), `"name":"operator-a"`) || !strings.Contains(session.Body.String(), `"role":"operator"`) {
|
||||
t.Fatalf("session status=%d body=%s", session.Code, session.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIAuthMisconfigurationFailsClosed(t *testing.T) {
|
||||
cases := []config.Config{
|
||||
{AuthMode: "enforce"},
|
||||
{AuthMode: "unknown"},
|
||||
{AuthMode: "enforce", AuthTokensJSON: `not-json`},
|
||||
{AuthMode: "enforce", AuthToken: "short"},
|
||||
}
|
||||
for _, cfg := range cases {
|
||||
rec := authRequest(t, cfg, http.MethodGet, "/api/v2/alerts/rules", "")
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("misconfigured auth should fail closed: cfg=%+v status=%d", cfg, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user