internal/httpd/reauth_test.go
159 lines · 4908 bytes
4 symbols in this file
1package httpd
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "strings"
8 "testing"
9 "time"
10
11 "gitbay.org/gitbay/internal/config"
12 "gitbay.org/gitbay/internal/control"
13 "gitbay.org/gitbay/internal/store"
14)
15
16// A session signed in longer ago than ReauthWindow cannot mint from the
17// settings page: the form comes back with the refusal and a sign-in
18// link, and the sign-in returns to /settings (#297).
19func TestWebMintNeedsRecentSignIn(t *testing.T) {
20 s, st, u := newTokenTestServer(t)
21 stale := u
22 stale.SignedInAt = time.Now().Add(-control.ReauthWindow - time.Minute)
23 rr := submitAccountForm(t, s, stale, url.Values{"field": {"token-create"}, "name": {"laptop"}, "scope": {"full"}})
24 if rr.Code != http.StatusSeeOther {
25 t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
26 }
27 if list, err := st.ListAPITokens(u.ID); err != nil || len(list) != 0 {
28 t.Fatalf("a stale session minted %+v (%v)", list, err)
29 }
30
31 req := httptest.NewRequest("GET", "/settings", nil)
32 for _, c := range rr.Result().Cookies() {
33 req.AddCookie(c)
34 }
35 page := httptest.NewRecorder()
36 s.accountPage(page, req, stale)
37 body := page.Body.String()
38 if !strings.Contains(body, control.ReauthRefusal) {
39 t.Fatalf("refusal not shown: %s", body)
40 }
41 if !strings.Contains(body, `<a href="/login">Sign in again</a>`) {
42 t.Fatalf("no sign-in link: %s", body)
43 }
44 var next string
45 for _, c := range page.Result().Cookies() {
46 if c.Name == nextCookie {
47 next = c.Value
48 }
49 }
50 if next != url.QueryEscape("/settings") {
51 t.Fatalf("gitbay_next = %q, want /settings", next)
52 }
53}
54
55// An API token has no browser session: minting through the API is not
56// held to the sign-in window.
57func TestAPIMintIgnoresTheSignInWindow(t *testing.T) {
58 s, st, u := newTokenTestServer(t)
59 if err := st.CreateAPIToken(u.ID, "ci", store.HashToken("gb_reauthtest"), "full", nil, 0); err != nil {
60 t.Fatal(err)
61 }
62 req := httptest.NewRequest("POST", "/api/v1/cmd",
63 strings.NewReader(`{"argv":["token","create","--name","second","--scope","read"]}`))
64 req.Header.Set("Authorization", "Bearer gb_reauthtest")
65 rr := httptest.NewRecorder()
66 s.apiCmd(rr, req)
67 if rr.Code != http.StatusOK {
68 t.Fatalf("status %d: %s", rr.Code, rr.Body.String())
69 }
70}
71
72// A fresh session mints without any refusal.
73func TestWebMintFreshSessionSucceeds(t *testing.T) {
74 s, st, u := newTokenTestServer(t)
75 rr := submitAccountForm(t, s, u, url.Values{"field": {"token-create"}, "name": {"laptop"}, "scope": {"full"}})
76 if rr.Code != http.StatusOK {
77 t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
78 }
79 if list, err := st.ListAPITokens(u.ID); err != nil || len(list) != 1 {
80 t.Fatalf("token not minted: %+v (%v)", list, err)
81 }
82}
83
84// A stale session posting a grant form (org members add, on the
85// organization's people page) also sees the refusal and the sign-in
86// link, and the membership is not created.
87func TestWebGrantNeedsRecentSignIn(t *testing.T) {
88 st, err := store.Open(":memory:")
89 if err != nil {
90 t.Fatal(err)
91 }
92 defer st.Close()
93 if err := st.MigrateUp(); err != nil {
94 t.Fatal(err)
95 }
96 uid, err := st.CreateUser("alice", false)
97 if err != nil {
98 t.Fatal(err)
99 }
100 if _, err := st.CreateUser("bob", false); err != nil {
101 t.Fatal(err)
102 }
103 fresh := store.User{ID: uid, Username: "alice", SignedInAt: time.Now()}
104 stale := fresh
105 stale.SignedInAt = time.Now().Add(-control.ReauthWindow - time.Minute)
106
107 cfg := config.Default()
108 cfg.Web.Mode = "accounts"
109 s := New(cfg, st, nil)
110 if _, msg, ok := s.runControl(fresh, []string{"org", "create", "krz"}); !ok {
111 t.Fatalf("org create: %s", msg)
112 }
113
114 req := httptest.NewRequest("POST", "/krz",
115 strings.NewReader(url.Values{"field": {"member-add"}, "user": {"bob"}}.Encode()))
116 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
117 req.SetPathValue("owner", "krz")
118 rr := httptest.NewRecorder()
119 s.orgSubmit(rr, req, stale)
120 if rr.Code != http.StatusSeeOther {
121 t.Fatalf("status %d, body %s", rr.Code, rr.Body.String())
122 }
123
124 req2 := httptest.NewRequest("GET", "/krz/-/people", nil)
125 req2.SetPathValue("owner", "krz")
126 for _, c := range rr.Result().Cookies() {
127 req2.AddCookie(c)
128 }
129 req2.AddCookie(sessionCookieFor(t, s, st, uid))
130 page := httptest.NewRecorder()
131 s.ownerProfile(page, req2)
132 body := page.Body.String()
133 if !strings.Contains(body, control.ReauthRefusal) {
134 t.Fatalf("refusal not shown: %s", body)
135 }
136 if !strings.Contains(body, `<a href="/login">Sign in again</a>`) {
137 t.Fatalf("no sign-in link: %s", body)
138 }
139 var next string
140 for _, c := range page.Result().Cookies() {
141 if c.Name == nextCookie {
142 next = c.Value
143 }
144 }
145 if next != url.QueryEscape("/krz/-/people") {
146 t.Fatalf("gitbay_next = %q, want /krz/-/people", next)
147 }
148
149 org, err := st.OrgByName("krz")
150 if err != nil {
151 t.Fatal(err)
152 }
153 members, _ := st.OrgMembers(org.ID)
154 for _, m := range members {
155 if m.Username == "bob" {
156 t.Fatalf("a stale session added bob to the org")
157 }
158 }
159}