go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/internal/perm/perm_test.go (about)

     1  // Copyright 2020 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 perm
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"google.golang.org/grpc/codes"
    22  
    23  	"go.chromium.org/luci/auth/identity"
    24  	"go.chromium.org/luci/gae/impl/memory"
    25  	"go.chromium.org/luci/gae/service/datastore"
    26  	"go.chromium.org/luci/grpc/appstatus"
    27  	"go.chromium.org/luci/server/auth"
    28  	"go.chromium.org/luci/server/auth/authtest"
    29  	"go.chromium.org/luci/server/auth/realms"
    30  	"go.chromium.org/luci/server/caching"
    31  
    32  	"go.chromium.org/luci/buildbucket/appengine/model"
    33  	"go.chromium.org/luci/buildbucket/bbperms"
    34  	pb "go.chromium.org/luci/buildbucket/proto"
    35  
    36  	. "github.com/smartystreets/goconvey/convey"
    37  )
    38  
    39  func testingContext() context.Context {
    40  	ctx := context.Background()
    41  	ctx = memory.Use(ctx)
    42  	ctx = caching.WithEmptyProcessCache(ctx)
    43  	return ctx
    44  }
    45  
    46  func TestHasInBucket(t *testing.T) {
    47  	t.Parallel()
    48  
    49  	Convey("With mocked auth DB", t, func() {
    50  		const (
    51  			anon   = identity.AnonymousIdentity
    52  			admin  = identity.Identity("user:admin@example.com")
    53  			reader = identity.Identity("user:reader@example.com")
    54  			writer = identity.Identity("user:writer@example.com")
    55  
    56  			appID            = "buildbucket-app-id"
    57  			projectID        = "some-project"
    58  			existingBucketID = "existing-bucket"
    59  			missingBucketID  = "missing-bucket"
    60  
    61  			existingRealmID = projectID + ":" + existingBucketID
    62  			missingRealmID  = projectID + ":" + missingBucketID
    63  		)
    64  
    65  		ctx := testingContext()
    66  
    67  		So(datastore.Put(ctx, &model.Bucket{
    68  			ID:     existingBucketID,
    69  			Parent: model.ProjectKey(ctx, projectID),
    70  			Proto:  &pb.Bucket{},
    71  		}), ShouldBeNil)
    72  
    73  		s := &authtest.FakeState{
    74  			FakeDB: authtest.NewFakeDB(
    75  				authtest.MockMembership(admin, Administrators),
    76  			),
    77  		}
    78  		ctx = auth.WithState(ctx, s)
    79  
    80  		check := func(bucketID string, perm realms.Permission, caller identity.Identity) codes.Code {
    81  			s.Identity = caller
    82  			err := HasInBucket(ctx, perm, projectID, bucketID)
    83  			if err == nil {
    84  				return codes.OK
    85  			}
    86  			status, ok := appstatus.Get(err)
    87  			if !ok {
    88  				return codes.Internal
    89  			}
    90  			return status.Code()
    91  		}
    92  
    93  		Convey("No realm ACLs", func() {
    94  			So(check(existingBucketID, bbperms.BuildsGet, anon), ShouldEqual, codes.NotFound)
    95  			So(check(existingBucketID, bbperms.BuildsGet, admin), ShouldEqual, codes.OK)
    96  			So(check(existingBucketID, bbperms.BuildsGet, reader), ShouldEqual, codes.NotFound)
    97  			So(check(existingBucketID, bbperms.BuildsGet, writer), ShouldEqual, codes.NotFound)
    98  
    99  			So(check(missingBucketID, bbperms.BuildsGet, anon), ShouldEqual, codes.NotFound)
   100  			So(check(missingBucketID, bbperms.BuildsGet, admin), ShouldEqual, codes.NotFound)
   101  			So(check(missingBucketID, bbperms.BuildsGet, reader), ShouldEqual, codes.NotFound)
   102  			So(check(missingBucketID, bbperms.BuildsGet, writer), ShouldEqual, codes.NotFound)
   103  		})
   104  
   105  		Convey("With realm ACLs", func() {
   106  			s.FakeDB.(*authtest.FakeDB).AddMocks(
   107  				authtest.MockPermission(reader, existingRealmID, bbperms.BuildersGet),
   108  				authtest.MockPermission(reader, existingRealmID, bbperms.BuildsGet),
   109  				authtest.MockPermission(writer, existingRealmID, bbperms.BuildersGet),
   110  				authtest.MockPermission(writer, existingRealmID, bbperms.BuildsGet),
   111  				authtest.MockPermission(writer, existingRealmID, bbperms.BuildsCancel),
   112  
   113  				authtest.MockPermission(reader, missingRealmID, bbperms.BuildersGet),
   114  				authtest.MockPermission(reader, missingRealmID, bbperms.BuildsGet),
   115  				authtest.MockPermission(writer, missingRealmID, bbperms.BuildersGet),
   116  				authtest.MockPermission(writer, missingRealmID, bbperms.BuildsGet),
   117  				authtest.MockPermission(writer, missingRealmID, bbperms.BuildsCancel),
   118  			)
   119  
   120  			Convey("Read perm", func() {
   121  				So(check(existingBucketID, bbperms.BuildsGet, anon), ShouldEqual, codes.NotFound)
   122  				So(check(existingBucketID, bbperms.BuildsGet, admin), ShouldEqual, codes.OK)
   123  				So(check(existingBucketID, bbperms.BuildsGet, reader), ShouldEqual, codes.OK)
   124  				So(check(existingBucketID, bbperms.BuildsGet, writer), ShouldEqual, codes.OK)
   125  			})
   126  
   127  			Convey("Write perm", func() {
   128  				So(check(existingBucketID, bbperms.BuildsCancel, anon), ShouldEqual, codes.NotFound)
   129  				So(check(existingBucketID, bbperms.BuildsCancel, admin), ShouldEqual, codes.OK)
   130  				So(check(existingBucketID, bbperms.BuildsCancel, reader), ShouldEqual, codes.PermissionDenied)
   131  				So(check(existingBucketID, bbperms.BuildsCancel, writer), ShouldEqual, codes.OK)
   132  			})
   133  
   134  			Convey("Missing bucket", func() {
   135  				So(check(missingBucketID, bbperms.BuildsGet, anon), ShouldEqual, codes.NotFound)
   136  				So(check(missingBucketID, bbperms.BuildsGet, admin), ShouldEqual, codes.NotFound)
   137  				So(check(missingBucketID, bbperms.BuildsGet, reader), ShouldEqual, codes.NotFound)
   138  				So(check(missingBucketID, bbperms.BuildsGet, writer), ShouldEqual, codes.NotFound)
   139  
   140  				So(check(missingBucketID, bbperms.BuildsCancel, anon), ShouldEqual, codes.NotFound)
   141  				So(check(missingBucketID, bbperms.BuildsCancel, admin), ShouldEqual, codes.NotFound)
   142  				So(check(missingBucketID, bbperms.BuildsCancel, reader), ShouldEqual, codes.NotFound)
   143  				So(check(missingBucketID, bbperms.BuildsCancel, writer), ShouldEqual, codes.NotFound)
   144  			})
   145  		})
   146  	})
   147  }
   148  
   149  func TestBucketsByPerm(t *testing.T) {
   150  	t.Parallel()
   151  
   152  	Convey("GetAccessibleBuckets", t, func() {
   153  		const reader = identity.Identity("user:reader@example.com")
   154  
   155  		ctx := testingContext()
   156  		ctx = auth.WithState(ctx, &authtest.FakeState{
   157  			Identity: reader,
   158  			FakeDB: authtest.NewFakeDB(
   159  				authtest.MockPermission(reader, "project:bucket1", bbperms.BuildersList),
   160  				authtest.MockPermission(reader, "project:bucket2", bbperms.BuildersList),
   161  				authtest.MockPermission(reader, "project2:bucket1", bbperms.BuildersList),
   162  				authtest.MockPermission(reader, "project:bucket2", bbperms.BuildsCancel),
   163  			),
   164  		})
   165  		datastore.GetTestable(ctx).AutoIndex(true)
   166  		datastore.GetTestable(ctx).Consistent(true)
   167  
   168  		So(datastore.Put(ctx,
   169  			&model.Bucket{
   170  				ID:     "bucket1",
   171  				Parent: model.ProjectKey(ctx, "project"),
   172  				Proto:  &pb.Bucket{},
   173  			},
   174  			&model.Bucket{
   175  				ID:     "bucket1",
   176  				Parent: model.ProjectKey(ctx, "project2"),
   177  				Proto:  &pb.Bucket{},
   178  			},
   179  			&model.Bucket{
   180  				ID:     "bucket2",
   181  				Parent: model.ProjectKey(ctx, "project"),
   182  				Proto:  &pb.Bucket{},
   183  			},
   184  		), ShouldBeNil)
   185  
   186  		buckets1, err := BucketsByPerm(ctx, bbperms.BuildersList, "")
   187  		So(err, ShouldBeNil)
   188  		So(buckets1, ShouldResemble, []string{"project/bucket1", "project/bucket2", "project2/bucket1"})
   189  
   190  		buckets2, err := BucketsByPerm(ctx, bbperms.BuildsCancel, "")
   191  		So(err, ShouldBeNil)
   192  		So(buckets2, ShouldResemble, []string{"project/bucket2"})
   193  
   194  		buckets3, err := BucketsByPerm(ctx, bbperms.BuildersList, "project2")
   195  		So(err, ShouldBeNil)
   196  		So(buckets3, ShouldResemble, []string{"project2/bucket1"})
   197  
   198  		ctx = auth.WithState(ctx, &authtest.FakeState{
   199  			Identity: identity.Identity("user:unknown@example.com"),
   200  		})
   201  		buckets4, err := BucketsByPerm(ctx, bbperms.BuildersList, "")
   202  		So(err, ShouldBeNil)
   203  		So(buckets4, ShouldBeNil)
   204  	})
   205  }