| 1 | package memoria |
| 2 | |
| 3 | import ( |
| 4 | "net/http" |
| 5 | "net/http/httptest" |
| 6 | "regexp" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | |
| 10 | "amadan.net/rastrillo/rastrillo" |
| 11 | ) |
| 12 | |
| 13 | // With Home configured, connect's middleware serves connect.FormPolicy: |
| 14 | // rastrillo's default policy with form-action widened to the Home issuer, |
| 15 | // and style-src kept at 'self' 'unsafe-inline'. Every other directive must |
| 16 | // be the pinned framework's, so a tightening upstream is not silently |
| 17 | // undone in Home mode. |
| 18 | // |
| 19 | // style-src is the one directive allowed to differ, and only in that one |
| 20 | // way: rastrillo fb806a2 refuses inline styles by default, connect (at the |
| 21 | // version pinned here) still allows them, and estilo's account-link page |
| 22 | // (homeconnect home_link.html, Home-only) draws the address with |
| 23 | // style="overflow-wrap: anywhere". The policy is connect's to tighten, |
| 24 | // not this app's to override. No hash or nonce may ride along: either |
| 25 | // would void 'unsafe-inline'. |
| 26 | func TestHomeCSPPreservesPinnedFrameworkDirectives(t *testing.T) { |
| 27 | mux := http.NewServeMux() |
| 28 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) |
| 29 | baselineHandler, closeBaseline, err := rastrillo.Handler(rastrillo.Options{Mux: mux}) |
| 30 | if err != nil { |
| 31 | t.Fatal(err) |
| 32 | } |
| 33 | if closeBaseline != nil { |
| 34 | defer closeBaseline() |
| 35 | } |
| 36 | rec := httptest.NewRecorder() |
| 37 | baselineHandler.ServeHTTP(rec, httptest.NewRequest("GET", homeTestOrigin+"/", nil)) |
| 38 | baseline := rec.Header().Get("Content-Security-Policy") |
| 39 | want := strings.Replace(baseline, "form-action 'self'", "form-action 'self' "+homeTestIssuer, 1) |
| 40 | if want == baseline { |
| 41 | t.Fatal("framework form-action changed") |
| 42 | } |
| 43 | styles := regexp.MustCompile(`style-src [^;]*`) |
| 44 | if !styles.MatchString(want) { |
| 45 | t.Fatal("framework policy has no style-src; this test's one exception no longer applies") |
| 46 | } |
| 47 | want = styles.ReplaceAllString(want, "style-src 'self' 'unsafe-inline'") |
| 48 | f := newHomeRouterFixture(t) |
| 49 | wrapped, done, err := rastrillo.Handler(rastrillo.Options{Mux: f.handler.(*http.ServeMux)}) |
| 50 | if err != nil { |
| 51 | t.Fatal(err) |
| 52 | } |
| 53 | if done != nil { |
| 54 | defer done() |
| 55 | } |
| 56 | for _, path := range []string{"/signin/local", "/auth/start", "/account/home", "/signed-out"} { |
| 57 | rec := httptest.NewRecorder() |
| 58 | wrapped.ServeHTTP(rec, httptest.NewRequest("GET", homeTestOrigin+path, nil)) |
| 59 | if got := rec.Header().Get("Content-Security-Policy"); got != want { |
| 60 | t.Fatalf("policy drift at %s: %q", path, got) |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |