go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/acls/run_read.go (about) 1 // Copyright 2021 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 acls 16 17 import ( 18 "context" 19 20 "google.golang.org/grpc/codes" 21 22 "go.chromium.org/luci/grpc/appstatus" 23 "go.chromium.org/luci/server/auth" 24 25 "go.chromium.org/luci/cv/internal/common" 26 "go.chromium.org/luci/cv/internal/run" 27 ) 28 29 // V0APIAllowGroup is a CRIA group with users that may make requests to v0 API. 30 const V0APIAllowGroup = "service-luci-change-verifier-v0-api-users" 31 32 // checkRunRead verifies that the calling user has read access to the Run. 33 // 34 // Returns true if user has. False, otherwise. 35 func checkRunRead(ctx context.Context, r *run.Run) (bool, error) { 36 // TODO(https://crbug.com/1233963): design & implement & test. 37 switch yes, err := checkLegacyCQStatusAccess(ctx, r.ID.LUCIProject()); { 38 case err != nil: 39 return false, err 40 case yes: 41 return true, nil 42 } 43 44 switch yes, err := auth.IsMember(ctx, V0APIAllowGroup); { 45 case err != nil: 46 return false, err 47 case yes: 48 return true, nil 49 } 50 // Default to no access. 51 return false, nil 52 } 53 54 // NewRunReadChecker returns a LoadRunChecker that checks read access 55 // for the Run to be loaded. 56 // 57 // If current identity lacks read access, ensures an appropriate appstatus 58 // package error is returned. 59 // 60 // Example: 61 // 62 // r, err := run.LoadRuns(ctx, id, acls.NewRunReadChecker()) 63 func NewRunReadChecker() run.LoadRunChecker { return runReadChecker{} } 64 65 // runNotFoundMsg is used as textual reason for gRPC NotFound code. 66 // 67 // Rationale: the caller shouldn't be able to distinguish between Run not 68 // existing and not having access to the Run, because it may leak the existence 69 // of the Run. 70 const runNotFoundMsg = "Run not found" 71 72 // runReadChecker checks read access to Runs. 73 type runReadChecker struct{} 74 75 // Before implements run.LoadRunChecker. 76 func (c runReadChecker) Before(ctx context.Context, id common.RunID) error { 77 return nil 78 } 79 80 // After implements run.LoadRunChecker. 81 func (c runReadChecker) After(ctx context.Context, r *run.Run) error { 82 if r == nil { 83 return appstatus.Error(codes.NotFound, runNotFoundMsg) 84 } 85 switch yes, err := checkRunRead(ctx, r); { 86 case err != nil: 87 return err 88 case yes: 89 return nil 90 default: 91 return appstatus.Error(codes.NotFound, runNotFoundMsg) 92 } 93 }