Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Refuse a protected-ref pattern that can never match

93c7cae8ebb7d6d853dca476851e2bd0cbe10079 · Keenan Nemetz · 18h ago · parent 2c988ecd

Refuse a protected-ref pattern that can never match

acl protect stored any string: refs/head/main read like a protected branch in
acl show while the hook compared it against refs/heads/main and matched
nothing, so approvals and required checks were off on the branch everything
lands on.

Validate the pattern in forge/access, where the rule is stored, and call it
from both acl protect add|set and the repository settings form. Only the two
shapes IsProtected understands are accepted, exact ref and one trailing
wildcard, the ref part must be a name git could store, and a singular
category is named as the typo it is.

server/admin_web_test.go +46

@@ -125,6 +125,52 @@ func TestRepositoryAccessSettings(t *testing.T) {
125 125 }
126 126 }
127 127
128 // A pattern the hook can never match must be refused by the form, not stored and
129 // echoed back by the settings page as if the branch were protected.
130 func TestRepositoryAccessRefusesAPatternThatCannotMatch(t *testing.T) {
131 t.Parallel()
132 f := browserFixture(t)
133 if _, err := f.srv.Identity.SetAdmin(f.ownerID, true, "test"); err != nil {
134 t.Fatal(err)
135 }
136 sess, cookie := f.browserSession(t, f.ownerID)
137
138 form := url.Values{
139 "csrf": {sess.CSRF},
140 "default": {"contribute"},
141 "approvals": {"1"},
142 "protected": {"refs/head/main"},
143 }
144 resp, body := f.browserPost(t, "/r/"+f.repo+"/settings/access", cookie, form)
145 if resp.StatusCode != 400 {
146 t.Fatalf("a pattern that cannot match was saved: %d", resp.StatusCode)
147 }
148 if !strings.Contains(body, "refs/heads/main") {
149 t.Fatalf("the refusal does not name the fix:\n%s", body)
150 }
151 m, err := f.srv.Repos.Open(f.repo)
152 if err != nil {
153 t.Fatal(err)
154 }
155 if acl, err := m.ACL(); err != nil {
156 t.Fatal(err)
157 } else if len(acl.Protected) != 0 {
158 t.Fatalf("the refusal stored %v", acl.Protected)
159 }
160
161 // A valid pattern still saves and round-trips through the page.
162 form.Set("protected", "refs/heads/main\nrefs/heads/release/*")
163 resp, _ = f.browserPost(t, "/r/"+f.repo+"/settings/access", cookie, form)
164 if resp.StatusCode != 303 {
165 t.Fatalf("valid save = %d", resp.StatusCode)
166 }
167 if acl, err := m.ACL(); err != nil {
168 t.Fatal(err)
169 } else if got := strings.Join(acl.Protected, ","); got != "refs/heads/main,refs/heads/release/*" {
170 t.Fatalf("protected = %q", got)
171 }
172 }
173
128 174 func TestInstanceAdminCanBrowsePrivateRepoWithoutACLEntry(t *testing.T) {
129 175 t.Parallel()
130 176 f := browserFixture(t)