go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/internal/git/gitacls/acls_test.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gitacls 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 22 "go.chromium.org/luci/appengine/gaetesting" 23 "go.chromium.org/luci/auth/identity" 24 "go.chromium.org/luci/config/validation" 25 configpb "go.chromium.org/luci/milo/proto/config" 26 "go.chromium.org/luci/server/auth" 27 "go.chromium.org/luci/server/auth/authtest" 28 29 . "github.com/smartystreets/goconvey/convey" 30 ) 31 32 func TestACLsWork(t *testing.T) { 33 t.Parallel() 34 c := gaetesting.TestingContext() 35 36 Convey("ACLs work", t, func() { 37 Convey("Validation works", func() { 38 validate := func(cfg ...*configpb.Settings_SourceAcls) error { 39 ctx := validation.Context{Context: c} 40 ctx.SetFile("settings.cfg") 41 ValidateConfig(&ctx, cfg) 42 return ctx.Finalize() 43 } 44 mustError := func(cfg ...*configpb.Settings_SourceAcls) multiError { 45 err := validate(cfg...) 46 So(err, ShouldNotBeNil) 47 return multiError(err.(*validation.Error).Errors) 48 } 49 50 valid := configpb.Settings_SourceAcls{ 51 Hosts: []string{"a.googlesource.com"}, 52 Projects: []string{"https://b.googlesource.com/c"}, 53 Readers: []string{"group:g", "user@example.com"}, 54 } 55 So(validate(&valid), ShouldBeNil) 56 57 mustError(&configpb.Settings_SourceAcls{}).with( 58 "at least 1 reader required", 59 "at least 1 host or project required", 60 ) 61 62 Convey("readers", func() { 63 mod := valid 64 mod.Readers = []string{"bad:kind", "group:", "user", "group:a", "group:a"} 65 mustError(&mod).with( 66 `invalid readers "bad:kind"`, 67 `invalid readers "group:": needs a group name`, 68 `invalid readers "user"`, 69 `duplicate`, 70 ) 71 }) 72 73 Convey("hosts", func() { 74 second := configpb.Settings_SourceAcls{ 75 Hosts: []string{ 76 valid.Hosts[0], 77 "example.com", 78 "repo.googlesource.com/repo", 79 "b.googlesource.com", // valid.Project was from here, and it's OK. 80 }, 81 Readers: []string{"group:a"}, 82 } 83 mustError(&valid, &second).with( 84 `host "a.googlesource.com"): has already been defined in source_acl #0`, 85 `isn't at *.googlesource.com`, 86 "shouldn't have path or fragment components", 87 ) 88 }) 89 90 Convey("projects", func() { 91 second := configpb.Settings_SourceAcls{ 92 Hosts: []string{"r.googlesource.com"}, 93 Projects: []string{ 94 valid.Projects[0], // dups of prev blocks are OK. 95 "r.googlesource.com/redundant", 96 "not-repo.googlesource.com", 97 "c.googlesource.com/a/repo.git#123", 98 "c-review.googlesource.com/src", 99 "https://\\meh", 100 valid.Projects[0], // dups of projects in this block is not OK. 101 }, 102 Readers: []string{"group:b"}, 103 } 104 mustError(&valid, &second).with( 105 `redundant because already covered by its host in the same source_acls block`, 106 `project "not-repo.googlesource.com"): should not be just a host`, 107 `should not contain '/a' path prefix`, 108 `should not contain '.git'`, 109 `shouldn't have fragment components`, 110 `must not be a Gerrit host (try without '-review')`, 111 `not a valid URL`, 112 `duplicate, already defined in the same source_acls block`, 113 ) 114 }) 115 }) 116 117 load := func(cfg ...*configpb.Settings_SourceAcls) *ACLs { 118 a, err := FromConfig(c, cfg) 119 if err != nil { 120 panic(err) // for stacktrace. 121 } 122 return a 123 } 124 125 Convey("Loading works", func() { 126 So(load( 127 &configpb.Settings_SourceAcls{ 128 Hosts: []string{"first.googlesource.com"}, 129 Projects: []string{ 130 "second.googlesource.com/y1", 131 "third.googlesource.com/z", 132 }, 133 Readers: []string{"user@example.com"}, 134 }), 135 ShouldResemble, 136 &ACLs{ 137 hosts: map[string]*hostACLs{ 138 "first.googlesource.com": { 139 itemACLs: itemACLs{ 140 definedIn: 0, 141 readers: []string{"user:user@example.com"}, 142 }, 143 }, 144 "second.googlesource.com": { 145 itemACLs: itemACLs{definedIn: -1}, 146 projects: map[string]*itemACLs{ 147 "y1": { 148 definedIn: 0, 149 readers: []string{"user:user@example.com"}, 150 }, 151 }, 152 }, 153 "third.googlesource.com": { 154 itemACLs: itemACLs{definedIn: -1}, 155 projects: map[string]*itemACLs{ 156 "z": { 157 definedIn: 0, 158 readers: []string{"user:user@example.com"}, 159 }, 160 }, 161 }, 162 }, 163 }) 164 165 So(load( 166 &configpb.Settings_SourceAcls{ 167 Hosts: []string{"first.googlesource.com"}, 168 Projects: []string{ 169 "second.googlesource.com/y1", 170 "third.googlesource.com/z", 171 }, 172 Readers: []string{"user@example.com"}, 173 }, 174 &configpb.Settings_SourceAcls{ 175 Hosts: []string{"third.googlesource.com"}, 176 Projects: []string{ 177 "first.googlesource.com/x", 178 "second.googlesource.com/y1", 179 "second.googlesource.com/y2", 180 }, 181 Readers: []string{"group:g"}, 182 }), 183 ShouldResemble, 184 &ACLs{ 185 hosts: map[string]*hostACLs{ 186 "first.googlesource.com": { 187 itemACLs: itemACLs{ 188 definedIn: 0, 189 readers: []string{"user:user@example.com"}, 190 }, 191 projects: map[string]*itemACLs{ 192 "x": { 193 definedIn: 1, 194 readers: []string{"group:g"}, 195 }, 196 }, 197 }, 198 "second.googlesource.com": { 199 itemACLs: itemACLs{definedIn: -1}, 200 projects: map[string]*itemACLs{ 201 "y1": { 202 definedIn: 1, 203 readers: []string{"group:g", "user:user@example.com"}, 204 }, 205 "y2": { 206 definedIn: 1, 207 readers: []string{"group:g"}, 208 }, 209 }, 210 }, 211 "third.googlesource.com": { 212 itemACLs: itemACLs{ 213 definedIn: 1, 214 readers: []string{"group:g"}, 215 }, 216 projects: map[string]*itemACLs{ 217 "z": { 218 definedIn: 0, 219 readers: []string{"user:user@example.com"}, 220 }, 221 }, 222 }, 223 }, 224 }) 225 }) 226 227 Convey("IsAllowed works", func() { 228 acls := load( 229 &configpb.Settings_SourceAcls{ 230 Hosts: []string{"public.googlesource.com"}, 231 Projects: []string{"limited.googlesource.com/public"}, 232 Readers: []string{"group:all"}, 233 }, 234 &configpb.Settings_SourceAcls{ 235 Hosts: []string{"limited.googlesource.com"}, 236 Projects: []string{"c.googlesource.com/private"}, 237 Readers: []string{"group:some", "they@example.com"}, 238 }, 239 ) 240 granted := func(ctx context.Context, host, project string) bool { 241 r, err := acls.IsAllowed(ctx, host, project) 242 if err != nil { 243 panic(err) 244 } 245 return r 246 } 247 248 cAnon := auth.WithState(c, &authtest.FakeState{ 249 Identity: identity.AnonymousIdentity, 250 IdentityGroups: []string{"all"}, 251 }) 252 So(granted(cAnon, "public.googlesource.com", "any"), ShouldBeTrue) 253 So(granted(cAnon, "limited.googlesource.com", "public"), ShouldBeTrue) 254 So(granted(cAnon, "limited.googlesource.com", "any"), ShouldBeFalse) 255 256 cThey := auth.WithState(c, &authtest.FakeState{ 257 Identity: "user:they@example.com", 258 IdentityGroups: []string{"all"}, 259 }) 260 So(granted(cThey, "limited.googlesource.com", "public"), ShouldBeTrue) 261 So(granted(cThey, "limited.googlesource.com", "any"), ShouldBeTrue) 262 So(granted(cThey, "c.googlesource.com", "private"), ShouldBeTrue) 263 So(granted(cThey, "c.googlesource.com", "nope"), ShouldBeFalse) 264 265 cSome := auth.WithState(c, &authtest.FakeState{ 266 Identity: "user:some@example.com", 267 IdentityGroups: []string{"some", "all"}, 268 }) 269 So(granted(cSome, "limited.googlesource.com", "any"), ShouldBeTrue) 270 So(granted(cSome, "c.googlesource.com", "private"), ShouldBeTrue) 271 So(granted(cSome, "c.googlesource.com", "nope"), ShouldBeFalse) 272 273 Convey("for Gerrit, too", func() { 274 So(granted(cAnon, "public-review.googlesource.com", "any"), ShouldBeTrue) 275 So(granted(cThey, "limited-review.googlesource.com", "any"), ShouldBeTrue) 276 So(granted(cSome, "c-review.googlesource.com", "private"), ShouldBeTrue) 277 }) 278 }) 279 }) 280 } 281 282 type multiError []error 283 284 func (m multiError) with(substrings ...string) { 285 for i, err := range m { 286 if i >= len(substrings) { 287 So(fmt.Errorf("extra errors produced: %q", m[i:]), ShouldBeNil) 288 } else { 289 So(err.Error(), ShouldContainSubstring, substrings[i]) 290 } 291 } 292 if len(substrings) > len(m) { 293 panic(fmt.Errorf("not produced errors: %q", substrings[len(m):])) 294 } 295 }