package notices import ( "log/slog" "net/http" "net/http/httptest" "path/filepath" "testing" ) func testHandler(t *testing.T, cfg HandlerConfig) http.Handler { t.Helper() store, err := Open(filepath.Join(t.TempDir(), "notices.yaml")) if err != nil { t.Fatalf("Open: %v", err) } t.Cleanup(store.Close) cfg.Store = store if cfg.Logger == nil { cfg.Logger = slog.Default() } h, err := NewHandler(cfg) if err != nil { t.Fatalf("NewHandler: %v", err) } return h } // Without RequiredGroup, the handler behaves exactly as it always did: // reaching it at all is enough, regardless of any group header. func TestHandlerNoGroupGateByDefault(t *testing.T) { h := testHandler(t, HandlerConfig{}) req := httptest.NewRequest("GET", "/admin/notices", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } } // With RequiredGroup set, a caller whose GroupHeader doesn't list it is // refused before the handler does anything else -- no CSRF cookie, no page. func TestHandlerRejectsMissingGroup(t *testing.T) { h := testHandler(t, HandlerConfig{RequiredGroup: "gsg-admins"}) cases := []struct { name string groups string }{ {"no header", ""}, {"different group", "some-other-group"}, {"prefix collision", "gsg-admins-extra"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest("GET", "/admin/notices", nil) if tc.groups != "" { req.Header.Set("X-Authentik-Groups", tc.groups) } rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusForbidden { t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden) } if rec.Result().Cookies() != nil && len(rec.Result().Cookies()) != 0 { t.Errorf("rejected request set cookies: %v", rec.Result().Cookies()) } }) } } // A member -- anywhere in the "|"-delimited list, with incidental // whitespace -- is let through. func TestHandlerAllowsMemberGroup(t *testing.T) { h := testHandler(t, HandlerConfig{RequiredGroup: "gsg-admins"}) cases := []string{ "gsg-admins", "other-group|gsg-admins", "gsg-admins|other-group", "other-group| gsg-admins |another", } for _, groups := range cases { req := httptest.NewRequest("GET", "/admin/notices", nil) req.Header.Set("X-Authentik-Groups", groups) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("groups=%q: status = %d, want %d", groups, rec.Code, http.StatusOK) } } } // A custom GroupHeader is honored instead of the Authentik default. func TestHandlerCustomGroupHeader(t *testing.T) { h := testHandler(t, HandlerConfig{RequiredGroup: "gsg-admins", GroupHeader: "X-Custom-Groups"}) req := httptest.NewRequest("GET", "/admin/notices", nil) req.Header.Set("X-Authentik-Groups", "gsg-admins") // wrong header, should not count rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusForbidden { t.Fatalf("status = %d, want %d (wrong header should not authorize)", rec.Code, http.StatusForbidden) } req = httptest.NewRequest("GET", "/admin/notices", nil) req.Header.Set("X-Custom-Groups", "gsg-admins") rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) } }