oficina / home Public

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

Plain git — no account needed to clone.

Download

Download this file

1package home
2
3import (
4 "bytes"
5 "context"
6 "encoding/xml"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "strings"
11 "testing"
12
13 "amadan.net/oficina/home/internal/home/directorydata"
14)
15
16func continuationTargets(t *testing.T, body string) (refresh, links []string) {
17 t.Helper()
18 d := xml.NewDecoder(strings.NewReader(body))
19 d.Strict = false
20 d.AutoClose = xml.HTMLAutoClose
21 d.Entity = xml.HTMLEntity
22 for {
23 token, err := d.Token()
24 if err == io.EOF {
25 break
26 }
27 if err != nil {
28 t.Fatal(err)
29 }
30 el, ok := token.(xml.StartElement)
31 if !ok {
32 continue
33 }
34 a := map[string]string{}
35 for _, v := range el.Attr {
36 a[v.Name.Local] = v.Value
37 }
38 if el.Name.Local == "meta" && strings.EqualFold(a["http-equiv"], "refresh") {
39 refresh = append(refresh, a["content"])
40 }
41 if el.Name.Local == "a" {
42 if a["referrerpolicy"] != "no-referrer" {
43 t.Fatal("anchor sends referrer")
44 }
45 links = append(links, a["href"])
46 }
47 }
48 return
49}
50func assertDocsContinuation(t *testing.T, r *httptest.ResponseRecorder, want string) {
51 t.Helper()
52 noStore(t, r)
53 assertContinuationPage(t, r, want)
54}
55func assertContinuationPage(t *testing.T, r *httptest.ResponseRecorder, want string) {
56 t.Helper()
57 if r.Header().Get("Cache-Control") != "no-store" {
58 t.Fatal("cacheable continuation")
59 }
60 if r.Code != 200 || r.Header().Get("Location") != "" || r.Header().Get("Refresh") != "" || r.Header().Get("Content-Type") != "text/html; charset=utf-8" || r.Header().Get("Referrer-Policy") != "no-referrer" {
61 t.Fatal(r.Code, r.Header())
62 }
63 refresh, links := continuationTargets(t, r.Body.String())
64 if len(refresh) != 1 || refresh[0] != "0;url="+want || len(links) != 1 || links[0] != want || strings.Contains(r.Body.String(), "ZgotmplZ") || strings.Contains(r.Body.String(), "<script") {
65 t.Fatal(refresh, links, r.Body.String())
66 }
67}
68func TestDocsContinueCanonicalResponseBoundary(t *testing.T) {
69 f := newChooserFixture(t)
70 c, err := f.p.directory.newDocsChooser(f.store)
71 if err != nil {
72 t.Fatal(err)
73 }
74 for _, origin := range []string{"https://alpha.docs.oficina.works", "https://approved.fixture.example"} {
75 r := httptest.NewRecorder()
76 c.continueToDocs(r, httptest.NewRequest("POST", directoryOrigin+docsChoicePath, nil), directorydata.Choice{Origin: origin})
77 assertDocsContinuation(t, r, origin+"/")
78 }
79 for _, origin := range []string{"", "http://alpha.example", "https://Alpha.example", "https://alpha.example/", "https://alpha.example:443", "https://user@alpha.example", "https://alpha.example?x=1", "https://alpha.example#f", "https://alpha.example?", "https://alpha.example\n", " https://alpha.example", "https://alpha.example\";url=https://bad.example", "javascript:alert(1)", "//alpha.example", "https://[::1]", "https://alpha.example/%22"} {
80 r := httptest.NewRecorder()
81 c.continueToDocs(r, httptest.NewRequest("POST", directoryOrigin+docsChoicePath, nil), directorydata.Choice{Origin: origin})
82 if r.Code != 503 || strings.Contains(r.Body.String(), "http-equiv") || r.Header().Get("Location") != "" {
83 t.Fatal(origin, r.Code, r.Body.String())
84 }
85 }
86}
87func TestDocsContinueRendererEscapesIdenticalTargets(t *testing.T) {
88 f := newChooserFixture(t)
89 // Exercise real template contexts independently of the fixed runtime URL.
90 want := "https://alpha.docs.oficina.works/auth/start?return_to=%2F&probe=%22%3C"
91 r := httptest.NewRecorder()
92 f.p.directory.render(r, httptest.NewRequest("GET", directoryOrigin+"/", nil), 200, "docs_continue.html", struct{ Destination, Title string }{want, "Continue to Docs"})
93 refresh, links := continuationTargets(t, r.Body.String())
94 if len(refresh) != 1 || refresh[0] != "0;url="+want || len(links) != 1 || links[0] != want || !strings.Contains(r.Body.String(), "&amp;probe=") {
95 t.Fatal(refresh, links, r.Body.String())
96 }
97 for name, page := range f.p.directory.renderer.pages {
98 if name == "docs_continue.html" {
99 continue
100 }
101 var b bytes.Buffer
102 _ = page.ExecuteTemplate(&b, "layout", map[string]any{})
103 if strings.Contains(strings.ToLower(b.String()), `http-equiv="refresh"`) {
104 t.Fatalf("refresh leaked to %s", name)
105 }
106 }
107}
108func TestDocsContinueRequeriesCurrentOrigin(t *testing.T) {
109 f := newChooserFixture(t)
110 cookie := f.session(t, chooserPerson)
111 id := f.team(t, "A1", "alpha", "Alpha", chooserPerson)
112 f.team(t, "B2", "beta", "Beta", chooserPerson)
113 if r := f.request("GET", docsEntryPath, "", "", cookie); r.Code != 200 {
114 t.Fatal(r.Code)
115 }
116 if _, err := f.p.directory.db.Writer().Exec(`UPDATE teams SET label='changed' WHERE id=?`, id); err != nil {
117 t.Fatal(err)
118 }
119 if _, err := f.p.directory.db.Writer().Exec(`UPDATE team_instances SET origin='https://changed.docs.oficina.works' WHERE team_id=?`, id); err != nil {
120 t.Fatal(err)
121 }
122 assertDocsContinuation(t, f.request("POST", docsChoicePath, directoryOrigin, "team=A1", cookie), "https://changed.docs.oficina.works/")
123}
124func TestDocsContinueExactHostAndExpiredSession(t *testing.T) {
125 f := newChooserFixture(t)
126 cookie := f.session(t, chooserPerson)
127 f.team(t, "A1", "alpha", "Alpha", chooserPerson)
128 for _, method := range []string{"GET", "POST"} {
129 path := docsEntryPath
130 if method == "POST" {
131 path = docsChoicePath
132 }
133 r := httptest.NewRequest(method, directoryOrigin+path, strings.NewReader("team=A1"))
134 r.Host = "wrong.example"
135 r.AddCookie(cookie)
136 r.Header.Set("Origin", directoryOrigin)
137 r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
138 w := httptest.NewRecorder()
139 f.handler.ServeHTTP(w, r)
140 if w.Code != 404 {
141 t.Fatal(method, w.Code)
142 }
143 }
144 if _, err := f.p.directory.db.Writer().ExecContext(context.Background(), `UPDATE sessions SET expires_at='2000-01-01T00:00:00Z'`); err != nil {
145 t.Fatal(err)
146 }
147 r := f.request(http.MethodPost, docsChoicePath, directoryOrigin, "team=A1", cookie)
148 assertChooserSigninContinuation(t, r, cookie.Name)
149}
150