Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The LUCI Authors. | 1 // Copyright 2017 The LUCI Authors. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with 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 | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 package acl | 15 package acl |
| 16 | 16 |
| 17 import ( | 17 import ( |
| 18 » "golang.org/x/net/context" | 18 » "context" |
| 19 » "fmt" | |
| 20 » "regexp" | |
| 21 » "sort" | |
| 22 » "strings" | |
| 19 | 23 |
| 24 "github.com/luci/luci-go/scheduler/appengine/messages" | |
| 20 "github.com/luci/luci-go/server/auth" | 25 "github.com/luci/luci-go/server/auth" |
| 21 ) | 26 ) |
| 22 | 27 |
| 28 // IsJobOwner is deprecated. UIse GrantsByRole.IsOwner instead. | |
| 23 func IsJobOwner(c context.Context, projectID, jobName string) bool { | 29 func IsJobOwner(c context.Context, projectID, jobName string) bool { |
| 24 // TODO(vadimsh): Do real ACLs. | 30 // TODO(vadimsh): Do real ACLs. |
| 25 ok, err := auth.IsMember(c, "administrators") | 31 ok, err := auth.IsMember(c, "administrators") |
| 26 if err != nil { | 32 if err != nil { |
| 27 panic(err) | 33 panic(err) |
| 28 } | 34 } |
| 29 return ok | 35 return ok |
| 30 } | 36 } |
| 37 | |
| 38 // GrantsByRole can answer questions who can READ and who OWNS the task. | |
| 39 type GrantsByRole struct { | |
| 40 Owners []string `gae:",noindex"` | |
| 41 Readers []string `gae:",noindex"` | |
| 42 } | |
| 43 | |
| 44 func (g *GrantsByRole) IsOwner(c context.Context) (bool, error) { | |
| 45 return hasGrant(c, g.Owners, groupsAdministrators) | |
| 46 } | |
| 47 | |
| 48 func (g *GrantsByRole) IsReader(c context.Context) (bool, error) { | |
| 49 return hasGrant(c, g.Owners, g.Readers, groupsAdministrators) | |
| 50 } | |
| 51 | |
| 52 func (g *GrantsByRole) Equal(o *GrantsByRole) bool { | |
| 53 eqSlice := func(a, b []string) bool { | |
| 54 if len(a) != len(b) { | |
| 55 return false | |
| 56 } | |
| 57 for i := range a { | |
| 58 if a[i] != b[i] { | |
| 59 return false | |
| 60 } | |
| 61 } | |
| 62 return true | |
| 63 } | |
| 64 return eqSlice(g.Owners, o.Owners) && eqSlice(g.Readers, o.Readers) | |
| 65 } | |
| 66 | |
| 67 // AclSets are parsed and indexed `AclSet` of a project. | |
| 68 type AclSets map[string][]*messages.Acl | |
| 69 | |
| 70 // ValidateAclSets validates list of AclSet of a project and returns AclSets. | |
| 71 func ValidateAclSets(sets []*messages.AclSet) (AclSets, error) { | |
|
Vadim Sh.
2017/08/01 01:56:19
you may or may not be interested in this thing: ht
tandrii(chromium)
2017/08/01 22:50:01
Neat! I'm punting it to another CL to refactor cat
Vadim Sh.
2017/08/01 23:04:09
No.
| |
| 72 as := make(AclSets, len(sets)) | |
| 73 for _, s := range sets { | |
| 74 if s.Name == "" { | |
| 75 return nil, fmt.Errorf("missing 'name' field'") | |
| 76 } | |
| 77 if !aclSetNameRe.MatchString(s.Name) { | |
| 78 return nil, fmt.Errorf("%q is not valid value for 'name' field", s.Name) | |
| 79 } | |
| 80 if _, isDup := as[s.Name]; isDup { | |
| 81 return nil, fmt.Errorf("aclSet name %q is not unique", s .Name) | |
| 82 } | |
| 83 if len(s.GetAcls()) == 0 { | |
| 84 return nil, fmt.Errorf("aclSet %q has no entries", s.Nam e) | |
| 85 } | |
| 86 as[s.Name] = s.GetAcls() | |
| 87 } | |
| 88 return as, nil | |
| 89 } | |
| 90 | |
| 91 // ValidateTaskAcls validates task's ACLs and returns TaskAcls. | |
| 92 func ValidateTaskAcls(pSets AclSets, tSets []string, tAcls []*messages.Acl) (*Gr antsByRole, error) { | |
| 93 grantsLists := make([][]*messages.Acl, 0, 1+len(tSets)) | |
| 94 if err := validateGrants(tAcls); err != nil { | |
| 95 return nil, err | |
| 96 } | |
| 97 grantsLists = append(grantsLists, tAcls) | |
| 98 for _, set := range tSets { | |
| 99 grantsList, exists := pSets[set] | |
| 100 if !exists { | |
| 101 return nil, fmt.Errorf("referencing AclSet '%s' which do esn't exist", set) | |
| 102 } | |
| 103 grantsLists = append(grantsLists, grantsList) | |
| 104 } | |
| 105 mg := mergeGrants(grantsLists...) | |
| 106 if n := len(mg.Owners) + len(mg.Readers); n > maxGrantsPerJob { | |
| 107 return nil, fmt.Errorf("Job or Trigger can have at most %d acls, but %d given", maxGrantsPerJob, n) | |
| 108 } | |
| 109 return mg, nil | |
| 110 } | |
| 111 | |
| 112 //////////////////////////////////////////////////////////////////////////////// | |
| 113 | |
| 114 var ( | |
| 115 // aclSetNameRe is used to validate AclSet Name field. | |
| 116 aclSetNameRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`) | |
|
tandrii(chromium)
2017/08/01 22:50:01
changed here instead.
| |
| 117 // maxGrantsPerJob is how many different grants are specified for a job. | |
| 118 maxGrantsPerJob = 32 | |
| 119 | |
| 120 groupsAdministrators = []string{"group:administrators"} | |
| 121 ) | |
| 122 | |
| 123 func validateGrants(gs []*messages.Acl) error { | |
| 124 for _, g := range gs { | |
| 125 if g.GetRole() != messages.Acl_OWNER && g.GetRole() != messages. Acl_READER { | |
| 126 return fmt.Errorf("invalid role %q", g.GetRole()) | |
| 127 } | |
| 128 if g.GetGrantedTo() == "" { | |
| 129 return fmt.Errorf("missing granted_to for role %s", g.Ge tRole()) | |
| 130 } | |
|
Vadim Sh.
2017/08/01 01:56:18
please validate granted_to:
if it has prefix "gro
tandrii(chromium)
2017/08/01 22:50:01
Done.
| |
| 131 } | |
| 132 return nil | |
| 133 } | |
| 134 | |
| 135 // mergeGrants merges valid grants into GrantsByRole, removing and sorting dupli cates. | |
| 136 func mergeGrants(grantsLists ...[]*messages.Acl) *GrantsByRole { | |
| 137 all := map[messages.Acl_Role]map[string]bool{ | |
|
Vadim Sh.
2017/08/01 01:56:19
you may or may not be interested in https://godoc.
tandrii(chromium)
2017/08/01 22:50:01
indeed I am :) Done.
| |
| 138 messages.Acl_OWNER: {}, | |
| 139 messages.Acl_READER: {}, | |
| 140 } | |
| 141 for _, grantsList := range grantsLists { | |
| 142 for _, g := range grantsList { | |
| 143 all[g.GetRole()][g.GetGrantedTo()] = true | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 sortKeys := func(m map[string]bool) []string { | |
| 148 r := make([]string, 0, len(m)) | |
| 149 for k := range m { | |
| 150 r = append(r, k) | |
| 151 } | |
| 152 sort.Strings(r) | |
| 153 return r | |
| 154 } | |
| 155 return &GrantsByRole{ | |
| 156 Owners: sortKeys(all[messages.Acl_OWNER]), | |
| 157 Readers: sortKeys(all[messages.Acl_READER]), | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 // hasGrant is current user is covered by any given grants. | |
| 162 func hasGrant(c context.Context, grantsList ...[]string) (bool, error) { | |
| 163 groups := []string{} | |
| 164 for _, grants := range grantsList { | |
| 165 for _, grant := range grants { | |
| 166 parts := strings.SplitN(grant, ":", 2) | |
| 167 if len(parts) < 2 { | |
| 168 // grant is just email. | |
| 169 if auth.CurrentIdentity(c).Email() == grant { | |
|
Vadim Sh.
2017/08/01 01:56:19
this returns "" if CurretnIdentity is not of 'user
tandrii(chromium)
2017/08/01 22:50:01
Done & refactored.
| |
| 170 return true, nil | |
| 171 } | |
| 172 continue | |
| 173 } | |
| 174 kind, value := parts[0], parts[1] | |
| 175 // TODO(tandrii): other kinds. | |
| 176 switch kind { | |
| 177 case "group": | |
| 178 groups = append(groups, value) | |
| 179 default: | |
| 180 panic(fmt.Errorf("granted_to '%s' kind is not su pported", grant)) | |
|
Vadim Sh.
2017/08/01 01:56:19
just check auth.CurrentIdentity(c) == identity.Ide
tandrii(chromium)
2017/08/01 22:50:01
Done.
| |
| 181 } | |
| 182 } | |
| 183 } | |
| 184 return auth.IsMember(c, groups...) | |
| 185 } | |
| OLD | NEW |