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  }