go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/authdb/validation_test.go (about) 1 // Copyright 2015 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 authdb 16 17 import ( 18 "testing" 19 20 "go.chromium.org/luci/server/auth/authdb/internal/realmset" 21 "go.chromium.org/luci/server/auth/service/protocol" 22 23 . "github.com/smartystreets/goconvey/convey" 24 . "go.chromium.org/luci/common/testing/assertions" 25 ) 26 27 func TestValidation(t *testing.T) { 28 t.Parallel() 29 30 validate := func(db *protocol.AuthDB) error { 31 _, err := NewSnapshotDB(db, "", 0, true) 32 return err 33 } 34 35 Convey("Works", t, func() { 36 So(validate(&protocol.AuthDB{}), ShouldBeNil) 37 So(validate(&protocol.AuthDB{ 38 Groups: []*protocol.AuthGroup{ 39 {Name: "group"}, 40 }, 41 IpWhitelists: []*protocol.AuthIPWhitelist{ 42 {Name: "IP allowlist"}, 43 }, 44 }), ShouldBeNil) 45 }) 46 47 Convey("Bad group", t, func() { 48 So(validate(&protocol.AuthDB{ 49 Groups: []*protocol.AuthGroup{ 50 { 51 Name: "group", 52 Members: []string{"bad identity"}, 53 }, 54 }, 55 }), ShouldErrLike, "invalid identity") 56 }) 57 58 Convey("Bad IP allowlist", t, func() { 59 So(validate(&protocol.AuthDB{ 60 IpWhitelists: []*protocol.AuthIPWhitelist{ 61 { 62 Name: "IP allowlist", 63 Subnets: []string{"not a subnet"}, 64 }, 65 }, 66 }), ShouldErrLike, "bad IP allowlist") 67 }) 68 69 Convey("Bad SecurityConfig", t, func() { 70 So(validate(&protocol.AuthDB{ 71 SecurityConfig: []byte("not a serialized proto"), 72 }), ShouldErrLike, "failed to deserialize SecurityConfig") 73 }) 74 } 75 76 func TestValidateAuthGroup(t *testing.T) { 77 t.Parallel() 78 79 Convey("validateAuthGroup works", t, func() { 80 groups := map[string]*protocol.AuthGroup{ 81 "group1": { 82 Name: "group1", 83 Members: []string{"user:abc@example.com"}, 84 Globs: []string{"service:*"}, 85 Nested: []string{"group2"}, 86 }, 87 "group2": { 88 Name: "group2", 89 }, 90 } 91 So(validateAuthGroup("group1", groups), ShouldBeNil) 92 }) 93 94 Convey("validateAuthGroup bad identity", t, func() { 95 groups := map[string]*protocol.AuthGroup{ 96 "group1": { 97 Name: "group1", 98 Members: []string{"blah"}, 99 }, 100 } 101 So(validateAuthGroup("group1", groups), ShouldErrLike, "invalid identity") 102 }) 103 104 Convey("validateAuthGroup bad glob", t, func() { 105 groups := map[string]*protocol.AuthGroup{ 106 "group1": { 107 Name: "group1", 108 Globs: []string{"blah"}, 109 }, 110 } 111 So(validateAuthGroup("group1", groups), ShouldErrLike, "invalid glob") 112 }) 113 114 Convey("validateAuthGroup missing nested group", t, func() { 115 groups := map[string]*protocol.AuthGroup{ 116 "group1": { 117 Name: "group1", 118 Nested: []string{"missing"}, 119 }, 120 } 121 So(validateAuthGroup("group1", groups), ShouldErrLike, "unknown nested group") 122 }) 123 124 Convey("validateAuthGroup dependency cycle", t, func() { 125 groups := map[string]*protocol.AuthGroup{ 126 "group1": { 127 Name: "group1", 128 Nested: []string{"group1"}, 129 }, 130 } 131 So(validateAuthGroup("group1", groups), ShouldErrLike, "dependency cycle found") 132 }) 133 } 134 135 type groupGraph map[string][]string 136 137 func TestFindGroupCycle(t *testing.T) { 138 t.Parallel() 139 140 call := func(groups groupGraph) []string { 141 asProto := make(map[string]*protocol.AuthGroup) 142 for k, v := range groups { 143 asProto[k] = &protocol.AuthGroup{ 144 Name: k, 145 Nested: v, 146 } 147 } 148 return findGroupCycle("start", asProto) 149 } 150 151 Convey("Empty", t, func() { 152 So(call(groupGraph{"start": nil}), ShouldBeEmpty) 153 }) 154 155 Convey("No cycles", t, func() { 156 So(call(groupGraph{ 157 "start": []string{"A"}, 158 "A": []string{"B"}, 159 "B": []string{"C"}, 160 }), ShouldBeEmpty) 161 }) 162 163 Convey("Self reference", t, func() { 164 So(call(groupGraph{ 165 "start": []string{"start"}, 166 }), ShouldResemble, []string{"start"}) 167 }) 168 169 Convey("Simple cycle", t, func() { 170 So(call(groupGraph{ 171 "start": []string{"A"}, 172 "A": []string{"start"}, 173 }), ShouldResemble, []string{"start", "A"}) 174 }) 175 176 Convey("Long cycle", t, func() { 177 So(call(groupGraph{ 178 "start": []string{"A"}, 179 "A": []string{"B"}, 180 "B": []string{"C"}, 181 "C": []string{"start"}, 182 }), ShouldResemble, []string{"start", "A", "B", "C"}) 183 }) 184 185 Convey("Diamond no cycles", t, func() { 186 So(call(groupGraph{ 187 "start": []string{"A1", "A2"}, 188 "A1": []string{"B"}, 189 "A2": []string{"B"}, 190 "B": nil, 191 }), ShouldBeEmpty) 192 }) 193 194 Convey("Diamond with cycles", t, func() { 195 So(call(groupGraph{ 196 "start": []string{"A1", "A2"}, 197 "A1": []string{"B"}, 198 "A2": []string{"B"}, 199 "B": []string{"start"}, 200 }), ShouldResemble, []string{"start", "A1", "B"}) 201 }) 202 } 203 204 func TestValidateRealms(t *testing.T) { 205 t.Parallel() 206 207 validate := func(realms *protocol.Realms) error { 208 _, err := NewSnapshotDB(&protocol.AuthDB{Realms: realms}, "", 0, true) 209 return err 210 } 211 212 perm := &protocol.Permission{} 213 cond := &protocol.Condition{} 214 215 Convey("Works", t, func() { 216 So(validate(&protocol.Realms{ 217 ApiVersion: realmset.ExpectedAPIVersion, 218 Conditions: []*protocol.Condition{cond, cond}, 219 Permissions: []*protocol.Permission{perm, perm}, 220 Realms: []*protocol.Realm{ 221 { 222 Bindings: []*protocol.Binding{ 223 {Permissions: []uint32{0}}, 224 {Conditions: []uint32{0}}, 225 {Permissions: []uint32{0, 1}, Conditions: []uint32{0, 1}}, 226 }, 227 }, 228 }, 229 }), ShouldBeNil) 230 }) 231 232 Convey("Out of bounds permission", t, func() { 233 So(validate(&protocol.Realms{ 234 ApiVersion: realmset.ExpectedAPIVersion, 235 Conditions: []*protocol.Condition{cond, cond}, 236 Permissions: []*protocol.Permission{perm, perm}, 237 Realms: []*protocol.Realm{ 238 { 239 Bindings: []*protocol.Binding{ 240 {Permissions: []uint32{2}}, 241 }, 242 }, 243 }, 244 }), ShouldErrLike, "referencing out-of-bounds permission") 245 }) 246 247 Convey("Out of bounds condition", t, func() { 248 So(validate(&protocol.Realms{ 249 ApiVersion: realmset.ExpectedAPIVersion, 250 Conditions: []*protocol.Condition{cond, cond}, 251 Permissions: []*protocol.Permission{perm, perm}, 252 Realms: []*protocol.Realm{ 253 { 254 Bindings: []*protocol.Binding{ 255 {Conditions: []uint32{2}}, 256 }, 257 }, 258 }, 259 }), ShouldErrLike, "referencing out-of-bounds condition") 260 }) 261 }