Files
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

198 lines
7.2 KiB
Go

package auth
import (
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
)
func TestSessionIssueReadAndClear(t *testing.T) {
manager := NewSessionManager("pulse_test_session", time.Hour, true)
now := time.Now()
response := httptest.NewRecorder()
principal := Principal{Subject: "subject-1", Role: RoleViewer}
if err := manager.Issue(response, principal, now); err != nil {
t.Fatal(err)
}
if response.Header().Get("Set-Cookie") == "" {
t.Fatal("session cookie was not set")
}
if !response.Result().Cookies()[0].HttpOnly || !response.Result().Cookies()[0].Secure {
t.Fatal("session cookie is not hardened")
}
request := httptest.NewRequest("GET", "/", nil)
for _, cookie := range response.Result().Cookies() {
request.AddCookie(cookie)
}
got, ok := manager.Principal(request, now.Add(time.Minute))
if !ok || got != principal {
t.Fatalf("session principal = %#v, %v", got, ok)
}
clearResponse := httptest.NewRecorder()
manager.Clear(clearResponse, request)
if _, ok := manager.Principal(request, now.Add(time.Minute)); ok {
t.Fatal("cleared session remained valid")
}
}
func TestSessionExpires(t *testing.T) {
manager := NewSessionManager("pulse_test_session", time.Minute, false)
now := time.Now()
response := httptest.NewRecorder()
if err := manager.Issue(response, Principal{Subject: "subject-1", Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
request := httptest.NewRequest("GET", "/", nil)
request.AddCookie(response.Result().Cookies()[0])
if _, ok := manager.Principal(request, now.Add(2*time.Minute)); ok {
t.Fatal("expired session remained valid")
}
}
func TestSlidingSessionRenewsIdleDeadlineButHonorsAbsoluteExpiry(t *testing.T) {
manager := NewSlidingSessionManager("pulse_test_session", time.Minute, 3*time.Minute, true)
now := time.Now().UTC().Truncate(time.Second)
issued := httptest.NewRecorder()
if err := manager.Issue(issued, Principal{Subject: "wallboard", Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
cookie := issued.Result().Cookies()[0]
request := httptest.NewRequest(http.MethodGet, "/api/v1/system/status", nil)
request.AddCookie(cookie)
beforeWindow := httptest.NewRecorder()
if _, ok := manager.Authenticate(beforeWindow, request, now.Add(20*time.Second)); !ok {
t.Fatal("active session was rejected before renewal window")
}
if beforeWindow.Header().Get("Set-Cookie") != "" {
t.Fatal("session renewed before entering the bounded renewal window")
}
for _, offset := range []time.Duration{40 * time.Second, 80 * time.Second, 130 * time.Second} {
response := httptest.NewRecorder()
if _, ok := manager.Authenticate(response, request, now.Add(offset)); !ok {
t.Fatalf("active session was rejected at %s", offset)
}
renewed := response.Result().Cookies()
if len(renewed) != 1 || renewed[0].Value != cookie.Value || renewed[0].Expires.After(now.Add(3*time.Minute)) {
t.Fatalf("unsafe renewal at %s: %#v", offset, renewed)
}
}
if _, ok := manager.Authenticate(httptest.NewRecorder(), request, now.Add(3*time.Minute)); ok {
t.Fatal("sliding session exceeded its absolute expiry")
}
}
func TestSlidingSessionConcurrentRenewalKeepsTokenUsable(t *testing.T) {
manager := NewSlidingSessionManager("pulse_test_session", time.Minute, time.Hour, false)
now := time.Now().UTC()
issued := httptest.NewRecorder()
if err := manager.Issue(issued, Principal{Subject: "wallboard", Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
cookie := issued.Result().Cookies()[0]
const workers = 24
var wait sync.WaitGroup
errors := make(chan string, workers)
for index := 0; index < workers; index++ {
wait.Add(1)
go func() {
defer wait.Done()
request := httptest.NewRequest(http.MethodGet, "/api/v1/dashboards", nil)
request.AddCookie(cookie)
if _, ok := manager.Authenticate(httptest.NewRecorder(), request, now.Add(40*time.Second)); !ok {
errors <- "concurrent renewal rejected a valid token"
}
}()
}
wait.Wait()
close(errors)
for message := range errors {
t.Error(message)
}
request := httptest.NewRequest(http.MethodGet, "/api/v1/dashboards", nil)
request.AddCookie(cookie)
if _, ok := manager.Principal(request, now.Add(90*time.Second)); !ok {
t.Fatal("stable token was invalidated by concurrent renewal")
}
}
func TestSessionAuthenticationContextIsRevokedByClear(t *testing.T) {
manager := NewSlidingSessionManager("pulse_test_session", time.Minute, time.Hour, true)
now := time.Now().UTC()
issued := httptest.NewRecorder()
if err := manager.Issue(issued, Principal{Subject: "viewer", Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodGet, "/api/v1/live", nil)
request.AddCookie(issued.Result().Cookies()[0])
authentication, ok := manager.AuthenticateSession(httptest.NewRecorder(), request, now.Add(time.Second))
if !ok || authentication.Context == nil {
t.Fatal("session authentication context was not returned")
}
manager.Clear(httptest.NewRecorder(), request)
select {
case <-authentication.Context.Done():
case <-time.After(time.Second):
t.Fatal("cleared session context remained active")
}
}
func TestSessionAuthenticationContextEndsAtAbsoluteExpiry(t *testing.T) {
manager := NewSlidingSessionManager("pulse_test_session", 25*time.Millisecond, 25*time.Millisecond, true)
now := time.Now().UTC()
issued := httptest.NewRecorder()
if err := manager.Issue(issued, Principal{Subject: "viewer", Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodGet, "/api/v1/live", nil)
request.AddCookie(issued.Result().Cookies()[0])
authentication, ok := manager.AuthenticateSession(httptest.NewRecorder(), request, now)
if !ok {
t.Fatal("new session was rejected")
}
select {
case <-authentication.Context.Done():
case <-time.After(time.Second):
t.Fatal("session context exceeded its absolute deadline")
}
}
func TestSessionStoreEvictsOldestSessionsPerSubject(t *testing.T) {
manager := NewSlidingSessionManager("pulse_test_session", time.Hour, 24*time.Hour, true)
manager.MaxSessionsPerSubject = 3
now := time.Now().UTC()
for index := 0; index < 12; index++ {
if err := manager.Issue(httptest.NewRecorder(), Principal{Subject: "viewer", Role: RoleViewer}, now.Add(time.Duration(index)*time.Second)); err != nil {
t.Fatal(err)
}
}
if got := len(manager.sessions); got != 3 {
t.Fatalf("session store size = %d, want 3", got)
}
}
func TestSessionIssuePurgesExpiredEntriesAndHonorsGlobalCapacity(t *testing.T) {
manager := NewSessionManager("pulse_test_session", time.Minute, true)
manager.MaxSessions = 2
manager.MaxSessionsPerSubject = 2
now := time.Now().UTC()
for _, subject := range []string{"viewer-1", "viewer-2"} {
if err := manager.Issue(httptest.NewRecorder(), Principal{Subject: subject, Role: RoleViewer}, now); err != nil {
t.Fatal(err)
}
}
if err := manager.Issue(httptest.NewRecorder(), Principal{Subject: "viewer-3", Role: RoleViewer}, now); err == nil {
t.Fatal("session capacity was not enforced")
}
if err := manager.Issue(httptest.NewRecorder(), Principal{Subject: "viewer-3", Role: RoleViewer}, now.Add(2*time.Minute)); err != nil {
t.Fatalf("expired sessions were not purged: %v", err)
}
if got := len(manager.sessions); got != 1 {
t.Fatalf("session store size after purge = %d, want 1", got)
}
}