soteria/internal/server/auth_support_test.go

143 lines
5.2 KiB
Go
Raw Normal View History

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)
}
}
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)
}
}
var errSentinel = http.ErrAbortHandler