2026-04-20 18:23:55 -03:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"scm.bstein.dev/bstein/soteria/internal/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestAuthorizeCoversAuthModesAndGroupChecks(t *testing.T) {
|
|
|
|
|
t.Run("auth disabled", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{AuthRequired: false}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
identity, status, err := srv.authorize(req)
|
|
|
|
|
if err != nil || status != http.StatusOK || identity.Authenticated {
|
|
|
|
|
t.Fatalf("expected auth-disabled request to pass anonymously, got identity=%#v status=%d err=%v", identity, status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("bearer token", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{
|
|
|
|
|
AuthRequired: true,
|
|
|
|
|
AuthBearerTokens: []string{"atlas-secret"},
|
|
|
|
|
}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
req.Header.Set("Authorization", "Bearer atlas-secret")
|
|
|
|
|
identity, status, err := srv.authorize(req)
|
|
|
|
|
if err != nil || status != http.StatusOK || identity.User != "service-token" {
|
|
|
|
|
t.Fatalf("expected bearer token auth, got identity=%#v status=%d err=%v", identity, status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("forwarded headers without group restriction", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{AuthRequired: true}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
req.Header.Set("X-Forwarded-User", "brad")
|
|
|
|
|
req.Header.Set("X-Forwarded-Email", "brad@bstein.dev")
|
|
|
|
|
req.Header.Set("X-Forwarded-Groups", "/ops;/dev")
|
|
|
|
|
identity, status, err := srv.authorize(req)
|
|
|
|
|
if err != nil || status != http.StatusOK || identity.User != "brad" || len(identity.Groups) != 2 {
|
|
|
|
|
t.Fatalf("expected forwarded header auth, got identity=%#v status=%d err=%v", identity, status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("missing identity", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{AuthRequired: true}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
_, status, err := srv.authorize(req)
|
|
|
|
|
if err == nil || status != http.StatusUnauthorized {
|
|
|
|
|
t.Fatalf("expected unauthorized missing identity, got status=%d err=%v", status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("group allowed", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{
|
|
|
|
|
AuthRequired: true,
|
|
|
|
|
AllowedGroups: []string{"admin", "ops"},
|
|
|
|
|
}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
req.Header.Set("X-Auth-Request-User", "brad")
|
|
|
|
|
req.Header.Set("X-Auth-Request-Groups", "/dev,/ops")
|
|
|
|
|
identity, status, err := srv.authorize(req)
|
|
|
|
|
if err != nil || status != http.StatusOK || identity.User != "brad" {
|
|
|
|
|
t.Fatalf("expected allowed group auth, got identity=%#v status=%d err=%v", identity, status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("group forbidden", func(t *testing.T) {
|
|
|
|
|
srv := &Server{cfg: &config.Config{
|
|
|
|
|
AuthRequired: true,
|
|
|
|
|
AllowedGroups: []string{"admin"},
|
|
|
|
|
}}
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
req.Header.Set("X-Forwarded-User", "brad")
|
|
|
|
|
req.Header.Set("X-Forwarded-Groups", "/dev")
|
|
|
|
|
_, status, err := srv.authorize(req)
|
|
|
|
|
if err == nil || status != http.StatusForbidden {
|
|
|
|
|
t.Fatalf("expected forbidden group failure, got status=%d err=%v", status, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRequesterAndAuthzHelpers(t *testing.T) {
|
|
|
|
|
ctx := context.WithValue(context.Background(), authContextKey, authIdentity{
|
|
|
|
|
Authenticated: true,
|
|
|
|
|
User: "brad",
|
|
|
|
|
Email: "brad@bstein.dev",
|
|
|
|
|
})
|
|
|
|
|
if got := currentRequester(ctx); got != "brad" {
|
|
|
|
|
t.Fatalf("expected requester user brad, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx = context.WithValue(context.Background(), authContextKey, authIdentity{
|
|
|
|
|
Authenticated: true,
|
|
|
|
|
Email: "brad@bstein.dev",
|
|
|
|
|
})
|
|
|
|
|
if got := currentRequester(ctx); got != "brad@bstein.dev" {
|
|
|
|
|
t.Fatalf("expected requester email, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx = context.WithValue(context.Background(), authContextKey, authIdentity{Authenticated: true})
|
|
|
|
|
if got := currentRequester(ctx); got != "authenticated" {
|
|
|
|
|
t.Fatalf("expected generic authenticated requester, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got := currentRequester(context.Background()); got != "anonymous" {
|
|
|
|
|
t.Fatalf("expected anonymous requester, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got := authzReason(http.StatusUnauthorized, errSentinel); got != "unauthenticated" {
|
|
|
|
|
t.Fatalf("expected unauthenticated reason, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
if got := authzReason(http.StatusForbidden, errSentinel); got != "forbidden_group" {
|
|
|
|
|
t.Fatalf("expected forbidden reason, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
if got := authzReason(http.StatusBadGateway, errSentinel); got != "error" {
|
|
|
|
|
t.Fatalf("expected generic error reason, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
if got := authzReason(http.StatusOK, nil); got != "unknown" {
|
|
|
|
|
t.Fatalf("expected unknown nil-error reason, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 19:46:11 -03:00
|
|
|
func TestAuthHeaderAndGroupHelpers(t *testing.T) {
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
|
|
|
|
req.Header.Set("X-Secondary", "second")
|
|
|
|
|
req.Header.Set("X-Primary", " first ")
|
|
|
|
|
|
|
|
|
|
if got := firstHeader(req, "X-Primary", "X-Secondary"); got != "first" {
|
|
|
|
|
t.Fatalf("expected first populated header, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
if got := splitGroups(" /ops, dev ; qa "); len(got) != 3 {
|
|
|
|
|
t.Fatalf("expected split groups, got %#v", got)
|
|
|
|
|
}
|
|
|
|
|
if got := normalizeGroups([]string{" /ops ", "", "dev", " /qa"}); len(got) != 3 || got[0] != "ops" || got[2] != "qa" {
|
|
|
|
|
t.Fatalf("expected normalized groups, got %#v", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 18:23:55 -03:00
|
|
|
var errSentinel = http.ErrAbortHandler
|