From cbbc12a31ba632a6f6b1a6bd09ed1e3832e3881d Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 3 Jul 2024 13:37:26 +0200 Subject: [PATCH] Zanzana: Sync team memberships (#89983) * Zanzana: Use uid for users and teams * Zanzana: Team membership migrator --------- Co-authored-by: Alexander Zobnin --- .../accesscontrol/migrator/zanzana.go | 82 +++++++++++++++---- pkg/services/authz/zanzana/zanzana.go | 7 +- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index 2467b0039a0..426485bde23 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -3,7 +3,6 @@ package migrator import ( "context" "fmt" - "strconv" "strings" openfgav1 "github.com/openfga/api/proto/openfga/v1" @@ -30,9 +29,14 @@ type ZanzanaSynchroniser struct { func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...TupleCollector) *ZanzanaSynchroniser { // Append shared collectors that is used by both enterprise and oss - collectors = append(collectors, managedPermissionsCollector(store)) + collectors = append( + collectors, + teamMembershipCollector(store), + managedPermissionsCollector(store), + ) return &ZanzanaSynchroniser{ + client: client, log: log.New("zanzana.sync"), collectors: collectors, } @@ -75,21 +79,24 @@ func managedPermissionsCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { const collectorID = "managed" const query = ` - SELECT ur.user_id, p.action, p.kind, p.identifier, r.org_id FROM permission p - INNER JOIN role r on p.role_id = r.id - LEFT JOIN user_role ur on r.id = ur.role_id - LEFT JOIN team_role tr on r.id = tr.role_id - LEFT JOIN builtin_role br on r.id = br.role_id - WHERE r.name LIKE 'managed:%' - ` + SELECT u.uid as user_uid, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id + FROM permission p + INNER JOIN role r ON p.role_id = r.id + LEFT JOIN user_role ur ON r.id = ur.role_id + LEFT JOIN user u ON u.id = ur.user_id + LEFT JOIN team_role tr ON r.id = tr.role_id + LEFT JOIN team t ON tr.team_id = t.id + LEFT JOIN builtin_role br ON r.id = br.role_id + WHERE r.name LIKE 'managed:%' + ` type Permission struct { RoleName string `xorm:"role_name"` OrgID int64 `xorm:"org_id"` Action string `xorm:"action"` Kind string Identifier string - UserID int64 `xorm:"user_id"` - TeamID int64 `xorm:"user_id"` + UserUID string `xorm:"user_uid"` + TeamUID string `xorm:"team_uid"` } var permissions []Permission @@ -103,10 +110,10 @@ func managedPermissionsCollector(store db.DB) TupleCollector { for _, p := range permissions { var subject string - if p.UserID > 0 { - subject = zanzana.NewObject(zanzana.TypeUser, strconv.FormatInt(p.UserID, 10)) - } else if p.TeamID > 0 { - subject = zanzana.NewObject(zanzana.TypeTeam, strconv.FormatInt(p.TeamID, 10)) + if len(p.UserUID) > 0 { + subject = zanzana.NewObject(zanzana.TypeUser, p.UserUID) + } else if len(p.TeamUID) > 0 { + subject = zanzana.NewObject(zanzana.TypeTeam, p.TeamUID) } else { // FIXME(kalleep): Unsuported role binding (org role). We need to have basic roles in place continue @@ -126,3 +133,48 @@ func managedPermissionsCollector(store db.DB) TupleCollector { return nil } } + +func teamMembershipCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "team_membership" + const query = ` + SELECT t.uid as team_uid, u.uid as user_uid, tm.permission + FROM team_member tm + INNER JOIN team t ON tm.team_id = t.id + INNER JOIN user u ON tm.user_id = u.id + ` + + type membership struct { + TeamUID string `xorm:"team_uid"` + UserUID string `xorm:"user_uid"` + Permission int + } + + var memberships []membership + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&memberships) + }) + + if err != nil { + return err + } + + for _, m := range memberships { + tuple := &openfgav1.TupleKey{ + User: zanzana.NewObject(zanzana.TypeUser, m.UserUID), + Object: zanzana.NewObject(zanzana.TypeTeam, m.TeamUID), + } + + // Admin permission is 4 and member 0 + if m.Permission == 4 { + tuple.Relation = zanzana.RelationTeamAdmin + } else { + tuple.Relation = zanzana.RelationTeamMember + } + + tuples[collectorID] = append(tuples[collectorID], tuple) + } + + return nil + } +} diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 5d8ecd0c9d6..3062511a031 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -12,6 +12,11 @@ const ( TypeTeam string = "team" ) +const ( + RelationTeamMember string = "member" + RelationTeamAdmin string = "admin" +) + func NewObject(typ, id string) string { return fmt.Sprintf("%s:%s", typ, id) } @@ -49,7 +54,7 @@ func TranslateToTuple(user string, action, kind, identifier string, orgID int64) tuple.User = user tuple.Relation = relation - // UID in grafana are not guarantee to be unique across orgs so we need to scope them. + // Some uid:s in grafana are not guarantee to be unique across orgs so we need to scope them. if t.orgScoped { tuple.Object = NewScopedObject(t.typ, identifier, strconv.FormatInt(orgID, 10)) } else {