Chromium Code Reviews| Index: scheduler/appengine/acl/acl.go |
| diff --git a/scheduler/appengine/acl/acl.go b/scheduler/appengine/acl/acl.go |
| index 32a4f3f94d13b0a176204996f4fe8d366f733db4..f1fd52488dbc7670e94ba3f021e0ad1ecd69fe7e 100644 |
| --- a/scheduler/appengine/acl/acl.go |
| +++ b/scheduler/appengine/acl/acl.go |
| @@ -15,11 +15,17 @@ |
| package acl |
| import ( |
| - "golang.org/x/net/context" |
| + "context" |
| + "fmt" |
| + "regexp" |
| + "sort" |
| + "strings" |
| + "github.com/luci/luci-go/scheduler/appengine/messages" |
| "github.com/luci/luci-go/server/auth" |
| ) |
| +// IsJobOwner is deprecated. UIse GrantsByRole.IsOwner instead. |
| func IsJobOwner(c context.Context, projectID, jobName string) bool { |
| // TODO(vadimsh): Do real ACLs. |
| ok, err := auth.IsMember(c, "administrators") |
| @@ -28,3 +34,152 @@ func IsJobOwner(c context.Context, projectID, jobName string) bool { |
| } |
| return ok |
| } |
| + |
| +// GrantsByRole can answer questions who can READ and who OWNS the task. |
| +type GrantsByRole struct { |
| + Owners []string `gae:",noindex"` |
| + Readers []string `gae:",noindex"` |
| +} |
| + |
| +func (g *GrantsByRole) IsOwner(c context.Context) (bool, error) { |
| + return hasGrant(c, g.Owners, groupsAdministrators) |
| +} |
| + |
| +func (g *GrantsByRole) IsReader(c context.Context) (bool, error) { |
| + return hasGrant(c, g.Owners, g.Readers, groupsAdministrators) |
| +} |
| + |
| +func (g *GrantsByRole) Equal(o *GrantsByRole) bool { |
| + eqSlice := func(a, b []string) bool { |
| + if len(a) != len(b) { |
| + return false |
| + } |
| + for i := range a { |
| + if a[i] != b[i] { |
| + return false |
| + } |
| + } |
| + return true |
| + } |
| + return eqSlice(g.Owners, o.Owners) && eqSlice(g.Readers, o.Readers) |
| +} |
| + |
| +// AclSets are parsed and indexed `AclSet` of a project. |
| +type AclSets map[string][]*messages.Acl |
| + |
| +// ValidateAclSets validates list of AclSet of a project and returns AclSets. |
| +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.
|
| + as := make(AclSets, len(sets)) |
| + for _, s := range sets { |
| + if s.Name == "" { |
| + return nil, fmt.Errorf("missing 'name' field'") |
| + } |
| + if !aclSetNameRe.MatchString(s.Name) { |
| + return nil, fmt.Errorf("%q is not valid value for 'name' field", s.Name) |
| + } |
| + if _, isDup := as[s.Name]; isDup { |
| + return nil, fmt.Errorf("aclSet name %q is not unique", s.Name) |
| + } |
| + if len(s.GetAcls()) == 0 { |
| + return nil, fmt.Errorf("aclSet %q has no entries", s.Name) |
| + } |
| + as[s.Name] = s.GetAcls() |
| + } |
| + return as, nil |
| +} |
| + |
| +// ValidateTaskAcls validates task's ACLs and returns TaskAcls. |
| +func ValidateTaskAcls(pSets AclSets, tSets []string, tAcls []*messages.Acl) (*GrantsByRole, error) { |
| + grantsLists := make([][]*messages.Acl, 0, 1+len(tSets)) |
| + if err := validateGrants(tAcls); err != nil { |
| + return nil, err |
| + } |
| + grantsLists = append(grantsLists, tAcls) |
| + for _, set := range tSets { |
| + grantsList, exists := pSets[set] |
| + if !exists { |
| + return nil, fmt.Errorf("referencing AclSet '%s' which doesn't exist", set) |
| + } |
| + grantsLists = append(grantsLists, grantsList) |
| + } |
| + mg := mergeGrants(grantsLists...) |
| + if n := len(mg.Owners) + len(mg.Readers); n > maxGrantsPerJob { |
| + return nil, fmt.Errorf("Job or Trigger can have at most %d acls, but %d given", maxGrantsPerJob, n) |
| + } |
| + return mg, nil |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +var ( |
| + // aclSetNameRe is used to validate AclSet Name field. |
| + aclSetNameRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`) |
|
tandrii(chromium)
2017/08/01 22:50:01
changed here instead.
|
| + // maxGrantsPerJob is how many different grants are specified for a job. |
| + maxGrantsPerJob = 32 |
| + |
| + groupsAdministrators = []string{"group:administrators"} |
| +) |
| + |
| +func validateGrants(gs []*messages.Acl) error { |
| + for _, g := range gs { |
| + if g.GetRole() != messages.Acl_OWNER && g.GetRole() != messages.Acl_READER { |
| + return fmt.Errorf("invalid role %q", g.GetRole()) |
| + } |
| + if g.GetGrantedTo() == "" { |
| + return fmt.Errorf("missing granted_to for role %s", g.GetRole()) |
| + } |
|
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.
|
| + } |
| + return nil |
| +} |
| + |
| +// mergeGrants merges valid grants into GrantsByRole, removing and sorting duplicates. |
| +func mergeGrants(grantsLists ...[]*messages.Acl) *GrantsByRole { |
| + 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.
|
| + messages.Acl_OWNER: {}, |
| + messages.Acl_READER: {}, |
| + } |
| + for _, grantsList := range grantsLists { |
| + for _, g := range grantsList { |
| + all[g.GetRole()][g.GetGrantedTo()] = true |
| + } |
| + } |
| + |
| + sortKeys := func(m map[string]bool) []string { |
| + r := make([]string, 0, len(m)) |
| + for k := range m { |
| + r = append(r, k) |
| + } |
| + sort.Strings(r) |
| + return r |
| + } |
| + return &GrantsByRole{ |
| + Owners: sortKeys(all[messages.Acl_OWNER]), |
| + Readers: sortKeys(all[messages.Acl_READER]), |
| + } |
| +} |
| + |
| +// hasGrant is current user is covered by any given grants. |
| +func hasGrant(c context.Context, grantsList ...[]string) (bool, error) { |
| + groups := []string{} |
| + for _, grants := range grantsList { |
| + for _, grant := range grants { |
| + parts := strings.SplitN(grant, ":", 2) |
| + if len(parts) < 2 { |
| + // grant is just email. |
| + 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.
|
| + return true, nil |
| + } |
| + continue |
| + } |
| + kind, value := parts[0], parts[1] |
| + // TODO(tandrii): other kinds. |
| + switch kind { |
| + case "group": |
| + groups = append(groups, value) |
| + default: |
| + panic(fmt.Errorf("granted_to '%s' kind is not supported", 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.
|
| + } |
| + } |
| + } |
| + return auth.IsMember(c, groups...) |
| +} |