Ctrl K
ComunidadGuías…

internal/middleware/auth_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package middleware

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/sarathsp06/sparrow/internal/accessauth"
	"github.com/sarathsp06/sparrow/pkg/access"
	"github.com/sarathsp06/sparrow/pkg/access/httpauth"
	"github.com/sarathsp06/sparrow/pkg/access/memstore"
)

const testRealm = "00000000-0000-0000-0000-000000000001"

// newAuth builds the /v1 guard over an in-memory token store, with apiKey as
// the master key ("" = authentication disabled).
func newAuth(t testing.TB, apiKey string) (*Auth, *access.Service) {
	t.Helper()
	var roots []access.RootKey
	if apiKey != "" {
		roots = []access.RootKey{{Secret: apiKey, Realm: testRealm, Name: "master key"}}
	}
	svc, err := access.New(access.Config{Store: memstore.New(), RootKeys: roots, TokenPrefix: "sparrow_tk_"})
	if err != nil {
		t.Fatal(err)
	}
	return NewAuth(apiKey, svc, accessauth.Realm), svc
}

// guarded runs one request through auth and reports the status and the
// principal the handler saw.
func guarded(auth *Auth, set func(*http.Request)) (int, access.Principal, string) {
	var seen access.Principal
	h := auth.HTTPMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		seen, _ = httpauth.FromContext(r.Context())
		w.WriteHeader(http.StatusNoContent)
	}))
	req := httptest.NewRequest(http.MethodGet, "/v1/webhooks", nil)
	if set != nil {
		set(req)
	}
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, req)
	return rr.Code, seen, rr.Body.String()
}

func bearer(v string) func(*http.Request) {
	return func(r *http.Request) { r.Header.Set("Authorization", "Bearer "+v) }
}

func apiKey(v string) func(*http.Request) {
	return func(r *http.Request) { r.Header.Set(APIKeyHeader, v) }
}

func TestAuthMasterKeyAndFullAccessTokens(t *testing.T) {
	auth, svc := newAuth(t, "secret")
	if code, p, _ := guarded(auth, bearer("secret")); code != http.StatusNoContent || !p.Root {
		t.Fatalf("master key as bearer: %d %+v", code, p)
	}

	tok, secret, err := svc.CreateToken(context.Background(), access.CreateTokenRequest{Realm: testRealm, Name: "alice", CreatedBy: "master key"})
	if err != nil {
		t.Fatal(err)
	}
	for name, set := range map[string]func(*http.Request){"X-API-Key": apiKey(secret), "Bearer": bearer(secret)} {
		code, p, _ := guarded(auth, set)
		if code != http.StatusNoContent || p.TokenID != tok.ID || p.Name != "alice" {
			t.Fatalf("token via %s: %d %+v", name, code, p)
		}
	}

	_ = svc.RevokeToken(context.Background(), testRealm, tok.ID)
	code, _, body := guarded(auth, apiKey(secret))
	var e httpauth.ErrorBody
	_ = json.Unmarshal([]byte(body), &e)
	if code != http.StatusUnauthorized || e.Reason != "revoked" {
		t.Fatalf("revoked token: %d %s", code, body)
	}
}

func TestAuthRejectsConsumerTokensOnV1(t *testing.T) {
	auth, svc := newAuth(t, "secret")
	consumer := "acme"
	_, secret, _ := svc.CreateToken(context.Background(), access.CreateTokenRequest{Realm: testRealm, Scope: &consumer, Name: "acme ci", TTL: time.Hour, CreatedBy: "x"})
	if code, _, body := guarded(auth, apiKey(secret)); code != http.StatusForbidden {
		t.Fatalf("consumer token on /v1: %d %s, want 403", code, body)
	}
}

func TestAuthRejectsOtherRealms(t *testing.T) {
	auth, svc := newAuth(t, "secret")
	_, secret, _ := svc.CreateToken(context.Background(), access.CreateTokenRequest{Realm: "another-tenant", Name: "x", CreatedBy: "x"})
	if code, _, _ := guarded(auth, apiKey(secret)); code != http.StatusUnauthorized {
		t.Fatalf("foreign realm: %d, want 401", code)
	}
}

func TestAuthDisabledIsOpenWithAnonymousPrincipal(t *testing.T) {
	auth, _ := newAuth(t, "")
	code, p, _ := guarded(auth, nil)
	if code != http.StatusNoContent || p.Name != AnonymousName || !p.FullAccess() {
		t.Fatalf("disabled: %d %+v", code, p)
	}
}

func TestAPIKeyHTTPMiddlewareAcceptsHeader(t *testing.T) {
	auth, _ := newAuth(t, "secret")
	called := false
	handler := auth.HTTPMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		w.WriteHeader(http.StatusNoContent)
	}))

	req := httptest.NewRequest(http.MethodGet, "/webhook.EventService/ListEvents", nil)
	req.Header.Set(APIKeyHeader, "secret")
	rr := httptest.NewRecorder()

	handler.ServeHTTP(rr, req)

	if rr.Code != http.StatusNoContent {
		t.Fatalf("status = %d, want %d", rr.Code, http.StatusNoContent)
	}
	if !called {
		t.Fatal("next handler was not called")
	}
}

func TestAPIKeyHTTPMiddlewareRejectsQueryParameter(t *testing.T) {
	auth, _ := newAuth(t, "secret")
	called := false
	handler := auth.HTTPMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))

	req := httptest.NewRequest(http.MethodGet, "/webhook.EventService/ListEvents?api_key=secret", nil)
	rr := httptest.NewRecorder()

	handler.ServeHTTP(rr, req)

	if rr.Code != http.StatusUnauthorized {
		t.Fatalf("status = %d, want %d", rr.Code, http.StatusUnauthorized)
	}
	if called {
		t.Fatal("next handler was called")
	}
}

func TestAPIKeyHTTPMiddlewareBypassesExcludedPath(t *testing.T) {
	auth, _ := newAuth(t, "secret")
	auth.ExcludedPathPrefixes = []string{"/health"}
	called := false
	handler := auth.HTTPMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		w.WriteHeader(http.StatusNoContent)
	}))

	req := httptest.NewRequest(http.MethodGet, "/health", nil)
	rr := httptest.NewRecorder()

	handler.ServeHTTP(rr, req)

	if rr.Code != http.StatusNoContent {
		t.Fatalf("status = %d, want %d", rr.Code, http.StatusNoContent)
	}
	if !called {
		t.Fatal("next handler was not called")
	}
}