Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1196)

Unified Diff: scheduler/appengine/acl/acl.go

Issue 2986033003: [scheduler]: ACLs phase 1 - per Job ACL specification and enforcement. (Closed)
Patch Set: pcg Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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...)
+}

Powered by Google App Engine
This is Rietveld 408576698