This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user