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:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user