go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/internal/acl/project.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 "context" 19 "errors" 20 "fmt" 21 22 cfgcommonpb "go.chromium.org/luci/common/proto/config" 23 "go.chromium.org/luci/server/auth" 24 "go.chromium.org/luci/server/auth/realms" 25 ) 26 27 // CanReadProjects checks whether the requester can read the provided projects. 28 // 29 // Returns a bitmap that maps to the provided projects. 30 func CanReadProjects(ctx context.Context, projects []string) ([]bool, error) { 31 if len(projects) == 0 { 32 return nil, errors.New("expected non-empty projects list, got empty") 33 } 34 ret := make([]bool, len(projects)) 35 aclCfg, err := getACLCfgCached(ctx) 36 if err != nil { 37 return nil, err 38 } 39 for i, proj := range projects { 40 ret[i], err = checkProjectPerm(ctx, proj, ReadPermission, aclCfg) 41 if err != nil { 42 return nil, err 43 } 44 } 45 return ret, nil 46 } 47 48 // CanReadProject checks whether the requester can read the config for the 49 // provided project 50 func CanReadProject(ctx context.Context, project string) (bool, error) { 51 aclCfg, err := getACLCfgCached(ctx) 52 if err != nil { 53 return false, err 54 } 55 return checkProjectPerm(ctx, project, ReadPermission, aclCfg) 56 } 57 58 // CanValidateProject checks whether the requester can validate the config 59 // for the provided project. 60 func CanValidateProject(ctx context.Context, project string) (bool, error) { 61 aclCfg, err := getACLCfgCached(ctx) 62 if err != nil { 63 return false, err 64 } 65 // No actions are allowed if no read access to the Project 66 switch allowed, err := checkProjectPerm(ctx, project, ReadPermission, aclCfg); { 67 case err != nil: 68 return false, err 69 case !allowed: 70 return false, nil 71 } 72 73 return checkProjectPerm(ctx, project, ValidatePermission, aclCfg) 74 } 75 76 // CanReimportProject checks whether the requester can reimport the config for 77 // the provided project. 78 func CanReimportProject(ctx context.Context, project string) (bool, error) { 79 aclCfg, err := getACLCfgCached(ctx) 80 if err != nil { 81 return false, err 82 } 83 // No actions are allowed if no read access to the Project 84 switch allowed, err := checkProjectPerm(ctx, project, ReadPermission, aclCfg); { 85 case err != nil: 86 return false, err 87 case !allowed: 88 return false, nil 89 } 90 91 return checkProjectPerm(ctx, project, ReimportPermission, aclCfg) 92 } 93 94 func checkProjectPerm(ctx context.Context, project string, perm realms.Permission, aclCfg *cfgcommonpb.AclCfg) (bool, error) { 95 if project == "" { 96 return false, errors.New("expected non-empty project, got empty") 97 } 98 var equivalentGroup string 99 switch perm { 100 case ReadPermission: 101 equivalentGroup = aclCfg.GetProjectAccessGroup() 102 case ValidatePermission: 103 equivalentGroup = aclCfg.GetProjectValidationGroup() 104 case ReimportPermission: 105 equivalentGroup = aclCfg.GetProjectReimportGroup() 106 } 107 // Checking If a caller is allowed in the global level groups first. 108 // If the caller is allowed, no need to check per-project level perm. 109 if equivalentGroup != "" { 110 switch yes, err := auth.IsMember(ctx, equivalentGroup); { 111 case err != nil: 112 return false, fmt.Errorf("failed to perform membership check for group %q: %w", equivalentGroup, err) 113 case yes: 114 return true, nil 115 } 116 } 117 realm := realms.Join(project, realms.RootRealm) 118 switch yes, err := auth.HasPermission(ctx, perm, realm, nil); { 119 case err != nil: 120 return false, fmt.Errorf("failed to check permission %s in realm %q: %w", perm, realm, err) 121 default: 122 return yes, nil 123 } 124 }