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 }