package calls import ( "net/http" "net/http/httptest" "strings" "testing" "amadan.net/rastrillo/rastrillo" ) // TestHomeCSPPreservesPinnedFrameworkDirectives: with Home configured, // connect's middleware sends homeFormPolicy on every page in the group, // which is rastrillo's default policy (inline styles refused) with only // the issuer added to form-action, so Home mode is no looser than // standalone. func TestHomeCSPPreservesPinnedFrameworkDirectives(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) baselineHandler, closeBaseline, err := rastrillo.Handler(rastrillo.Options{Mux: mux}) if err != nil { t.Fatal(err) } if closeBaseline != nil { defer closeBaseline() } rec := httptest.NewRecorder() baselineHandler.ServeHTTP(rec, httptest.NewRequest("GET", homeTestOrigin+"/", nil)) baseline := rec.Header().Get("Content-Security-Policy") // homeFormPolicy starts from frameworkCSP, rastrillo's default spelled // out; a bump that changes the default must change it too. if baseline != frameworkCSP { t.Fatalf("rastrillo's default CSP is now %q; frameworkCSP still says %q", baseline, frameworkCSP) } want := strings.Replace(baseline, "form-action 'self'", "form-action 'self' "+homeTestIssuer, 1) if want == baseline { t.Fatal("framework form-action changed") } f := newHomeRouterFixture(t) wrapped, done, err := rastrillo.Handler(rastrillo.Options{Mux: f.handler.(*http.ServeMux)}) if err != nil { t.Fatal(err) } if done != nil { defer done() } for _, path := range []string{"/signin/local", "/auth/start", "/account/home", "/signed-out", "/auth/verify?token=x"} { rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, httptest.NewRequest("GET", homeTestOrigin+path, nil)) if got := rec.Header().Get("Content-Security-Policy"); got != want { t.Fatalf("policy drift at %s: %q", path, got) } } } func TestHomeMutationOriginsAndDuplicateCookies(t *testing.T) { f := newHomeRouterFixture(t) f.local(t) for _, path := range []string{"/signin", "/auth/verify", "/account/home", "/account/home/confirm", "/signout", "/meetings"} { r := httptest.NewRequest("POST", homeTestOrigin+path, strings.NewReader("address=alice%40example.test")) r.Header.Set("Origin", "https://other.example.test") for _, c := range f.cookies { r.AddCookie(c) } w := httptest.NewRecorder() f.handler.ServeHTTP(w, r) if w.Code != 403 { t.Fatal("cross-origin accepted", path, w.Code) } } r := httptest.NewRequest("GET", homeTestOrigin+"/account/home", nil) for _, c := range f.cookies { r.AddCookie(c) r.AddCookie(c) } w := httptest.NewRecorder() f.handler.ServeHTTP(w, r) if w.Code != 400 { t.Fatal("duplicate cookies accepted", w.Code) } } func TestHomeCredentialsFailClosedWhenPartial(t *testing.T) { f := newHomeRouterFixture(t) cfg := f.app.cfg.Config for _, name := range []string{"issuer", "consumer", "keys", "dek"} { broken := *cfg.Home switch name { case "issuer": broken.Issuer = "" case "consumer": broken.ConsumerID = "" case "keys": broken.KeysBase64 = "" case "dek": broken.DEKBase64 = "" } cfg.Home = &broken if _, err := resolve(cfg); err == nil { t.Fatal("partial accepted", name) } cfg = f.app.cfg.Config } }