go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/internal/acl/service_test.go (about)

     1  // Copyright 2023 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 acl
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/auth/identity"
    22  	cfgcommonpb "go.chromium.org/luci/common/proto/config"
    23  	"go.chromium.org/luci/gae/service/datastore"
    24  	"go.chromium.org/luci/server/auth"
    25  	"go.chromium.org/luci/server/auth/authtest"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.chromium.org/luci/config_service/internal/common"
    29  	"go.chromium.org/luci/config_service/internal/model"
    30  	"go.chromium.org/luci/config_service/testutil"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  )
    34  
    35  func TestServiceConfig(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	Convey("Service Config ACL Check", t, func() {
    39  		ctx := testutil.SetupContext()
    40  		fakeAuthDB := authtest.NewFakeDB()
    41  		const requester = identity.Identity("user:requester@example.com")
    42  		const service = "my-service"
    43  		ctx = auth.WithState(ctx, &authtest.FakeState{
    44  			Identity: requester,
    45  			FakeDB:   fakeAuthDB,
    46  		})
    47  		const accessGroup = "access-group"
    48  		const validationGroup = "validate-group"
    49  		const reimportGroup = "reimport-group"
    50  		testutil.InjectSelfConfigs(ctx, map[string]proto.Message{
    51  			common.ACLRegistryFilePath: &cfgcommonpb.AclCfg{
    52  				ServiceAccessGroup:     accessGroup,
    53  				ServiceValidationGroup: validationGroup,
    54  				ServiceReimportGroup:   reimportGroup,
    55  			},
    56  		})
    57  
    58  		Convey("CanReadService", func() {
    59  			Convey("Access Denied", func() {
    60  				allowed, err := CanReadService(ctx, service)
    61  				So(err, ShouldBeNil)
    62  				So(allowed, ShouldBeFalse)
    63  				Convey("Not even with consulting service config", func() {
    64  					srv := &model.Service{
    65  						Name: service,
    66  						Info: &cfgcommonpb.Service{
    67  							Id:     service,
    68  							Access: []string{"admin@example.com"},
    69  						},
    70  					}
    71  					So(datastore.Put(ctx, srv), ShouldBeNil)
    72  					allowed, err := CanReadService(ctx, service)
    73  					So(err, ShouldBeNil)
    74  					So(allowed, ShouldBeFalse)
    75  				})
    76  			})
    77  			Convey("Allowed in AccessGroup", func() {
    78  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
    79  				allowed, err := CanReadService(ctx, service)
    80  				So(err, ShouldBeNil)
    81  				So(allowed, ShouldBeTrue)
    82  			})
    83  			Convey("Allowed in service config", func() {
    84  				Convey("By group", func() {
    85  					srv := &model.Service{
    86  						Name: service,
    87  						Info: &cfgcommonpb.Service{
    88  							Id:     service,
    89  							Access: []string{"group:some-access-group"},
    90  						},
    91  					}
    92  					So(datastore.Put(ctx, srv), ShouldBeNil)
    93  					fakeAuthDB.AddMocks(authtest.MockMembership(requester, "some-access-group"))
    94  					allowed, err := CanReadService(ctx, service)
    95  					So(err, ShouldBeNil)
    96  					So(allowed, ShouldBeTrue)
    97  				})
    98  				Convey("By explicit identity", func() {
    99  					for _, prefix := range []string{"", "user:"} {
   100  						Convey(fmt.Sprintf("With prefix %q", prefix), func() {
   101  							srv := &model.Service{
   102  								Name: service,
   103  								Info: &cfgcommonpb.Service{
   104  									Id:     service,
   105  									Access: []string{prefix + requester.Email()},
   106  								},
   107  							}
   108  							So(datastore.Put(ctx, srv), ShouldBeNil)
   109  							allowed, err := CanReadService(ctx, service)
   110  							So(err, ShouldBeNil)
   111  							So(allowed, ShouldBeTrue)
   112  						})
   113  					}
   114  				})
   115  			})
   116  		})
   117  
   118  		Convey("CanReadServices", func() {
   119  			services := []string{"service1", "service2"}
   120  			result, err := CanReadServices(ctx, services)
   121  			So(err, ShouldBeNil)
   122  			So(result, ShouldResemble, []bool{false, false})
   123  			// allow access
   124  			fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
   125  			result, err = CanReadServices(ctx, services)
   126  			So(err, ShouldBeNil)
   127  			So(result, ShouldResemble, []bool{true, true})
   128  		})
   129  
   130  		Convey("CanValidateService", func() {
   131  			Convey("Denied because of no read access", func() {
   132  				// This should allow validate
   133  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, validationGroup))
   134  				allowed, err := CanValidateService(ctx, service)
   135  				So(err, ShouldBeNil)
   136  				So(allowed, ShouldBeFalse)
   137  			})
   138  			Convey("Denied even with read access", func() {
   139  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
   140  				allowed, err := CanValidateService(ctx, service)
   141  				So(err, ShouldBeNil)
   142  				So(allowed, ShouldBeFalse)
   143  			})
   144  			Convey("Allowed by ValidationGroup", func() {
   145  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
   146  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, validationGroup))
   147  				allowed, err := CanValidateService(ctx, service)
   148  				So(err, ShouldBeNil)
   149  				So(allowed, ShouldBeTrue)
   150  			})
   151  		})
   152  
   153  		Convey("CanReimportService", func() {
   154  			Convey("Denied because of no read access", func() {
   155  				// This should allow reimport
   156  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, reimportGroup))
   157  				allowed, err := CanReimportService(ctx, service)
   158  				So(err, ShouldBeNil)
   159  				So(allowed, ShouldBeFalse)
   160  			})
   161  			Convey("Denied even with read access", func() {
   162  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
   163  				allowed, err := CanReimportService(ctx, service)
   164  				So(err, ShouldBeNil)
   165  				So(allowed, ShouldBeFalse)
   166  			})
   167  			Convey("Allowed by ReimportGroup", func() {
   168  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, accessGroup))
   169  				fakeAuthDB.AddMocks(authtest.MockMembership(requester, reimportGroup))
   170  				allowed, err := CanReimportService(ctx, service)
   171  				So(err, ShouldBeNil)
   172  				So(allowed, ShouldBeTrue)
   173  			})
   174  		})
   175  	})
   176  }