oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package connect
2
3import (
4 "encoding/base64"
5 "encoding/json"
6 "errors"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12)
13
14func navigationRequest(path string) *http.Request {
15 return httptest.NewRequest("GET", navigationOrigin+path, nil)
16}
17func navigationMarker(t *testing.T, n *localLinkNavigation) *http.Cookie {
18 t.Helper()
19 w := httptest.NewRecorder()
20 n.Begin(w, navigationRequest("/account/home"))
21 for _, c := range w.Result().Cookies() {
22 if c.Name == localLinkNavigationCookie && c.MaxAge > 0 {
23 return c
24 }
25 }
26 t.Fatal("marker missing")
27 return nil
28}
29func assertNavigation(t *testing.T, n *localLinkNavigation, r *http.Request, want string) {
30 t.Helper()
31 rec := httptest.NewRecorder()
32 n.Complete(rec, r)
33 if rec.Code != 303 || rec.Header().Get("Location") != want || rec.Header().Get("Cache-Control") != "no-store" {
34 t.Fatalf("navigation status=%d target=%q wanted%q", rec.Code, rec.Header().Get("Location"), want)
35 }
36 cleared := false
37 for _, c := range rec.Result().Cookies() {
38 if c.Name == localLinkNavigationCookie && c.MaxAge < 0 {
39 cleared = true
40 }
41 }
42 if !cleared {
43 t.Fatal("marker not cleared")
44 }
45}
46
47func TestLocalNavigationRejectsInvalidMarkersAndOtherHosts(t *testing.T) {
48 local, n := navigationFixture(t)
49 now := time.Now().Truncate(time.Second)
50 local.At = now
51 n.now = func() time.Time { return now }
52 cookie := &http.Cookie{Name: n.sessionCookie, Value: "verified"}
53 valid := navigationMarker(t, n).Value
54 positive := navigationRequest(localLinkCompletePath)
55 positive.AddCookie(cookie)
56 positive.AddCookie(&http.Cookie{Name: localLinkNavigationCookie, Value: valid})
57 assertNavigation(t, n, positive, "/account/home")
58 plain := func(m localLinkMarker) string { b, _ := json.Marshal(m); return string(b) }
59 base := localLinkMarker{1, "link-start", now.Unix(), now.Unix() + 600}
60 badJSON := []string{
61 "{}", `{"v":1,"v":1,"action":"link-start","issued":1,"expires":2}`,
62 plain(base) + " {}", strings.TrimSuffix(plain(base), "}") + `,"unknown":true}`,
63 }
64 for _, m := range []localLinkMarker{{2, "link-start", now.Unix(), now.Unix() + 600}, {1, "other", now.Unix(), now.Unix() + 600}, {1, "link-start", 0, now.Unix() + 600}, {1, "link-start", now.Unix() + 1, now.Unix() + 600}, {1, "link-start", now.Unix() - 600, now.Unix()}, {1, "link-start", now.Unix(), now.Unix() + 601}} {
65 badJSON = append(badJSON, plain(m))
66 }
67 badValues := []string{"", strings.Repeat("a", 1025), valid + "=", "%" + valid, valid[:len(valid)-2]}
68 for _, p := range badJSON {
69 sealed, err := n.sealer.Seal(n.purpose(), []byte(p))
70 if err != nil {
71 t.Fatal(err)
72 }
73 badValues = append(badValues, base64.RawURLEncoding.EncodeToString(sealed))
74 }
75 otherPurpose, err := n.sealer.Seal(n.namespace+".local-link-navigation.v1:https://other.example", []byte(plain(base)))
76 if err != nil {
77 t.Fatal(err)
78 }
79 badValues = append(badValues, base64.RawURLEncoding.EncodeToString(otherPurpose))
80 for _, value := range badValues {
81 r := navigationRequest(localLinkCompletePath)
82 r.AddCookie(cookie)
83 r.AddCookie(&http.Cookie{Name: localLinkNavigationCookie, Value: value})
84 assertNavigation(t, n, r, "/")
85 }
86 for _, change := range []func(*http.Request){func(r *http.Request) { r.Host = "other.example" }, func(r *http.Request) { r.URL.RawQuery = "return_to=https://evil.example" }, func(r *http.Request) { r.URL.Path = "/wrong" }} {
87 r := navigationRequest(localLinkCompletePath)
88 r.AddCookie(cookie)
89 r.AddCookie(&http.Cookie{Name: localLinkNavigationCookie, Value: valid})
90 change(r)
91 assertNavigation(t, n, r, "/")
92 }
93 // A valid marker expires independently of the browser cookie's MaxAge.
94 n.now = func() time.Time { return now.Add(600 * time.Second) }
95 r := navigationRequest(localLinkCompletePath)
96 r.AddCookie(cookie)
97 r.AddCookie(&http.Cookie{Name: localLinkNavigationCookie, Value: valid})
98 assertNavigation(t, n, r, "/")
99}
100
101type navigationBrokenSealer struct{}
102
103func (navigationBrokenSealer) Seal(string, []byte) ([]byte, error) {
104 return nil, errors.New("private failure")
105}
106func (navigationBrokenSealer) Open(string, []byte) ([]byte, error) {
107 return nil, errors.New("private failure")
108}
109func TestLocalNavigationStorageFailureAndMethod(t *testing.T) {
110 _, n := navigationFixture(t)
111 n.sealer = navigationBrokenSealer{}
112 rec := httptest.NewRecorder()
113 n.Begin(rec, navigationRequest("/account/home"))
114 if rec.Code != 503 || strings.Contains(rec.Body.String(), "private") || rec.Header().Get("Location") != "" {
115 t.Fatal("storage failure published/leaked navigation")
116 }
117 for _, fn := range []http.HandlerFunc{n.Begin, n.Complete} {
118 rec = httptest.NewRecorder()
119 r := navigationRequest(localLinkCompletePath)
120 r.Method = "POST"
121 fn(rec, r)
122 if rec.Code != 405 || rec.Header().Get("Allow") != "GET" || rec.Header().Get("Cache-Control") != "no-store" {
123 t.Fatal("wrong method accepted")
124 }
125 }
126}
127