Zanzana: Model fixed roles as a part of schema (#92364)

* model fixed roles for dashboards and folders

* Correctly translate fixed role assignments

* minor refactor

* assign fixed roles to teams

* fix linter errors

* Migrate general folder permissions for fixed roles

* fix dashboards:create permission
This commit is contained in:
Alexander Zobnin
2024-08-27 15:39:22 +02:00
committed by GitHub
parent 8862578f50
commit 0e0c877609
4 changed files with 148 additions and 43 deletions
+108 -30
View File
@@ -45,6 +45,7 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu
basicRoleAssignemtCollector(store),
userRoleAssignemtCollector(store),
teamRoleAssignemtCollector(store),
fixedRoleTuplesCollector(store),
)
return &ZanzanaSynchroniser{
@@ -474,16 +475,18 @@ func userRoleAssignemtCollector(store db.DB) TupleCollector {
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
const collectorID = "user_role_assignment"
const query = `
SELECT ur.org_id, u.uid AS user_uid, r.uid AS role_uid
SELECT ur.org_id, u.uid AS user_uid, r.uid AS role_uid, r.name AS role_name
FROM user_role ur
LEFT JOIN role r ON r.id = ur.role_id
LEFT JOIN user u ON u.id = ur.user_id
WHERE r.name NOT LIKE 'managed:%'
`
type Assignment struct {
OrgID int64 `xorm:"org_id"`
UserUID string `xorm:"user_uid"`
RoleUID string `xorm:"role_uid"`
OrgID int64 `xorm:"org_id"`
UserUID string `xorm:"user_uid"`
RoleUID string `xorm:"role_uid"`
RoleName string `xorm:"role_name"`
}
var assignments []Assignment
@@ -495,21 +498,32 @@ func userRoleAssignemtCollector(store db.DB) TupleCollector {
}
for _, a := range assignments {
var subject string
if a.UserUID != "" && a.RoleUID != "" {
subject = zanzana.NewTupleEntry(zanzana.TypeUser, a.UserUID, "")
} else {
if a.UserUID == "" || a.RoleUID == "" {
continue
}
tuple := &openfgav1.TupleKey{
User: subject,
Relation: zanzana.RelationAssignee,
Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)),
}
subject := zanzana.NewTupleEntry(zanzana.TypeUser, a.UserUID, "")
if strings.HasPrefix(a.RoleUID, "fixed_") {
// Fixed roles are defined in shema, so they are relations itself. Assignment should look like:
// user:<uid> fixed_folders_reader org:1
relation := zanzana.TranslateFixedRole(a.RoleName)
tuple := &openfgav1.TupleKey{
User: subject,
Relation: relation,
Object: zanzana.NewTupleEntry(zanzana.TypeOrg, strconv.FormatInt(a.OrgID, 10), ""),
}
key := fmt.Sprintf("%s-%s", collectorID, relation)
tuples[key] = append(tuples[key], tuple)
} else {
tuple := &openfgav1.TupleKey{
User: subject,
Relation: zanzana.RelationAssignee,
Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)),
}
key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee)
tuples[key] = append(tuples[key], tuple)
key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee)
tuples[key] = append(tuples[key], tuple)
}
}
return nil
@@ -520,16 +534,18 @@ func teamRoleAssignemtCollector(store db.DB) TupleCollector {
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
const collectorID = "team_role_assignment"
const query = `
SELECT tr.org_id, t.uid AS team_uid, r.uid AS role_uid
SELECT tr.org_id, t.uid AS team_uid, r.uid AS role_uid, r.name AS role_name
FROM team_role tr
LEFT JOIN role r ON r.id = tr.role_id
LEFT JOIN team t ON t.id = tr.team_id
WHERE r.name NOT LIKE 'managed:%'
`
type Assignment struct {
OrgID int64 `xorm:"org_id"`
TeamUID string `xorm:"team_uid"`
RoleUID string `xorm:"role_uid"`
OrgID int64 `xorm:"org_id"`
TeamUID string `xorm:"team_uid"`
RoleUID string `xorm:"role_uid"`
RoleName string `xorm:"role_name"`
}
var assignments []Assignment
@@ -541,21 +557,83 @@ func teamRoleAssignemtCollector(store db.DB) TupleCollector {
}
for _, a := range assignments {
var subject string
if a.TeamUID != "" && a.RoleUID != "" {
subject = zanzana.NewTupleEntry(zanzana.TypeTeam, a.TeamUID, "member")
} else {
if a.TeamUID == "" || a.RoleUID == "" {
continue
}
tuple := &openfgav1.TupleKey{
User: subject,
Relation: zanzana.RelationAssignee,
Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)),
}
subject := zanzana.NewTupleEntry(zanzana.TypeTeam, a.TeamUID, "member")
if strings.HasPrefix(a.RoleUID, "fixed_") {
// Fixed roles are defined in shema, so they are relations itself. Assignment should look like:
// team:<uid> fixed_folders_reader org:1
relation := zanzana.TranslateFixedRole(a.RoleName)
tuple := &openfgav1.TupleKey{
User: subject,
Relation: relation,
Object: zanzana.NewTupleEntry(zanzana.TypeOrg, strconv.FormatInt(a.OrgID, 10), ""),
}
key := fmt.Sprintf("%s-%s", collectorID, relation)
tuples[key] = append(tuples[key], tuple)
} else {
tuple := &openfgav1.TupleKey{
User: subject,
Relation: zanzana.RelationAssignee,
Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)),
}
key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee)
tuples[key] = append(tuples[key], tuple)
key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee)
tuples[key] = append(tuples[key], tuple)
}
}
return nil
}
}
// fixedRoleTuplesCollector migrates fixed roles permissions that cannot be described in schema.
// Those are permissions like general folder read and create that have specific resource id.
func fixedRoleTuplesCollector(store db.DB) TupleCollector {
return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error {
const collectorID = "fixed_role"
type Org struct {
Id int64
Name string
}
var orgs []Org
orgsQuery := "SELECT id, name FROM org"
err := store.WithDbSession(ctx, func(sess *db.Session) error {
return sess.SQL(orgsQuery).Find(&orgs)
})
if err != nil {
return err
}
type Assignment struct {
RoleName string
Action string
Kind string
Identifier string
}
assignments := []Assignment{
{RoleName: "fixed:dashboards:creator", Action: "folders:read", Kind: "folders", Identifier: "general"},
{RoleName: "fixed:dashboards:creator", Action: "dashboards:create", Kind: "folders", Identifier: "general"},
{RoleName: "fixed:folders:creator", Action: "folders:create", Kind: "folders", Identifier: "general"},
{RoleName: "fixed:folders.general:reader", Action: "folders:read", Kind: "folders", Identifier: "general"},
}
for _, a := range assignments {
fixedRole := zanzana.TranslateFixedRole(a.RoleName)
subject := zanzana.NewTupleEntry(zanzana.TypeRole, fixedRole, "assignee")
for _, org := range orgs {
tuple, ok := zanzana.TranslateToTuple(subject, a.Action, a.Kind, a.Identifier, org.Id)
if !ok {
continue
}
key := fmt.Sprintf("%s-%s", collectorID, a.Action)
tuples[key] = append(tuples[key], tuple)
}
}
return nil
@@ -2,17 +2,28 @@ module dashboard
extend type org
relations
define dashboard_create: [role#assignee] or fixed_folders_writer or fixed_dashboards_creator
define dashboard_read: [role#assignee] or fixed_folders_reader or fixed_folders_writer or fixed_dashboards_reader
define dashboard_write: [role#assignee] or fixed_folders_writer or fixed_dashboards_writer
define dashboard_delete: [role#assignee] or fixed_folders_writer or fixed_dashboards_writer
define dashboard_permissions_read: [role#assignee] or fixed_folders_writer or fixed_dashboards_permissions_reader
define dashboard_permissions_write: [role#assignee] or fixed_folders_writer or fixed_dashboards_permissions_writer
define dashboard_public_write: [role#assignee] or dashboard_write or fixed_dashboards_public_writer
define dashboard_annotations_create: [role#assignee]
define dashboard_annotations_read: [role#assignee]
define dashboard_annotations_write: [role#assignee]
define dashboard_annotations_delete: [role#assignee]
define dashboard_create: [role#assignee]
define dashboard_delete: [role#assignee]
define dashboard_permissions_read: [role#assignee]
define dashboard_permissions_write: [role#assignee]
define dashboard_public_write: [role#assignee] or dashboard_write
define dashboard_read: [role#assignee]
define dashboard_write: [role#assignee]
# Fixed roles
define fixed_dashboards_creator: [user, team#member, role#assignee] or fixed_dashboards_writer
define fixed_dashboards_reader: [user, team#member, role#assignee] or fixed_dashboards_writer
define fixed_dashboards_writer: [user, team#member, role#assignee]
define fixed_dashboards_insights_reader: [user, team#member, role#assignee]
define fixed_dashboards_public_writer: [user, team#member, role#assignee]
define fixed_dashboards_permissions_reader: [user, team#member, role#assignee] or fixed_dashboards_permissions_writer
define fixed_dashboards_permissions_writer: [user, team#member, role#assignee]
type dashboard
relations
+15 -6
View File
@@ -2,12 +2,12 @@ module folder
extend type org
relations
define folder_create: [role#assignee]
define folder_read: [role#assignee] or folder_delete
define folder_write: [role#assignee] or folder_create
define folder_delete: [role#assignee] or folder_write
define folder_permissions_write: [role#assignee]
define folder_permissions_read: [role#assignee] or folder_permissions_write
define folder_create: [role#assignee] or fixed_folders_creator
define folder_read: [role#assignee] or folder_delete or fixed_folders_reader
define folder_write: [role#assignee] or folder_create or fixed_folders_writer
define folder_delete: [role#assignee] or folder_write or fixed_folders_writer
define folder_permissions_read: [role#assignee] or folder_permissions_write or fixed_folders_permissions_reader
define folder_permissions_write: [role#assignee] or fixed_folders_permissions_writer
define library_panel_create: [role#assignee]
define library_panel_read: [role#assignee] or library_panel_write
@@ -23,6 +23,15 @@ extend type org
define alert_silence_read: [role#assignee] or alert_silence_write
define alert_silence_write: [role#assignee] or alert_silence_create
# Fixed roles
define fixed_folders_creator: [user, team#member, role#assignee] or fixed_folders_writer
define fixed_folders_reader: [user, team#member, role#assignee] or fixed_folders_writer
define fixed_folders_writer: [user, team#member, role#assignee]
define fixed_folders_permissions_reader: [user, team#member, role#assignee] or fixed_folders_permissions_writer
define fixed_folders_permissions_writer: [user, team#member, role#assignee]
# Read General (root) folder only
define fixed_folders_general_reader: [user, team#member, role#assignee]
type folder
relations
define parent: [folder]
+7
View File
@@ -3,6 +3,7 @@ package zanzana
import (
"fmt"
"strconv"
"strings"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
)
@@ -111,3 +112,9 @@ func TranslateToOrgTuple(user string, action string, orgID int64) (*openfgav1.Tu
func TranslateBasicRole(role string) string {
return basicRolesTranslations[role]
}
func TranslateFixedRole(role string) string {
role = strings.ReplaceAll(role, ":", "_")
role = strings.ReplaceAll(role, ".", "_")
return role
}