Public source validation / validate (push) Failing after 3m8s
198 lines
7.2 KiB
Go
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)
|
|
}
|
|
}
|