Zanzana: Refactor basic role write APIs (#113397)
* Zanzana: Refactor basic role write APIs * Fix updates * fix linter
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@ message MutateOperation {
|
||||
DeletePermissionOperation delete_permission = 4;
|
||||
UpdateUserOrgRoleOperation update_user_org_role = 5;
|
||||
DeleteUserOrgRoleOperation delete_user_org_role = 6;
|
||||
AddUserOrgRoleOperation add_user_org_role = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +64,14 @@ message DeletePermissionOperation {
|
||||
Permission permission = 2;
|
||||
}
|
||||
|
||||
message AddUserOrgRoleOperation {
|
||||
// User UID
|
||||
string user = 1;
|
||||
// Role name (e.g: "Admin", "Editor", "Viewer")
|
||||
string role = 2;
|
||||
}
|
||||
|
||||
// UpdateUserOrgRoleOperation assigns the user's basic role and deletes existing basic role assignments.
|
||||
message UpdateUserOrgRoleOperation {
|
||||
// User UID
|
||||
string user = 1;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
|
||||
dashboards "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||
@@ -23,6 +25,14 @@ var basicRolesTranslations = map[string]string{
|
||||
roleNone: "basic_none",
|
||||
}
|
||||
|
||||
var basicRolesUIDs = []string{
|
||||
"basic_grafana_admin",
|
||||
"basic_admin",
|
||||
"basic_editor",
|
||||
"basic_viewer",
|
||||
"basic_none",
|
||||
}
|
||||
|
||||
type resourceTranslation struct {
|
||||
typ string
|
||||
group string
|
||||
@@ -149,3 +159,7 @@ func TranslateToGroupResource(kind string) string {
|
||||
func TranslateBasicRole(name string) string {
|
||||
return basicRolesTranslations[name]
|
||||
}
|
||||
|
||||
func IsBasicRole(name string) bool {
|
||||
return slices.Contains(basicRolesUIDs, name)
|
||||
}
|
||||
|
||||
@@ -465,3 +465,21 @@ func AddRenderContext(req *openfgav1.CheckRequest) {
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
func SplitTupleObject(object string) (string, string, string) {
|
||||
var objectType, name, relation string
|
||||
parts := strings.Split(object, ":")
|
||||
if len(parts) < 2 {
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
objectType = parts[0]
|
||||
nameRel := parts[1]
|
||||
parts = strings.Split(nameRel, "#")
|
||||
if len(parts) > 1 {
|
||||
relation = parts[1]
|
||||
}
|
||||
name = parts[0]
|
||||
|
||||
return objectType, name, relation
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func getOperationGroup(operation *authzextv1.MutateOperation) (OperationGroup, e
|
||||
return OperationGroupFolder, nil
|
||||
case *authzextv1.MutateOperation_CreatePermission, *authzextv1.MutateOperation_DeletePermission:
|
||||
return OperationGroupPermission, nil
|
||||
case *authzextv1.MutateOperation_UpdateUserOrgRole, *authzextv1.MutateOperation_DeleteUserOrgRole:
|
||||
case *authzextv1.MutateOperation_UpdateUserOrgRole, *authzextv1.MutateOperation_DeleteUserOrgRole, *authzextv1.MutateOperation_AddUserOrgRole:
|
||||
return OperationGroupUserOrgRole, nil
|
||||
}
|
||||
return OperationGroup(""), errors.New("unsupported mutate operation type")
|
||||
|
||||
@@ -18,18 +18,29 @@ func (s *Server) mutateOrgRoles(ctx context.Context, store *storeInfo, operation
|
||||
|
||||
for _, operation := range operations {
|
||||
switch op := operation.Operation.(type) {
|
||||
case *authzextv1.MutateOperation_UpdateUserOrgRole:
|
||||
tuple, err := s.getUserOrgRoleWriteTuple(ctx, store, op.UpdateUserOrgRole)
|
||||
if err != nil {
|
||||
return err
|
||||
case *authzextv1.MutateOperation_AddUserOrgRole:
|
||||
basicRole := zanzana.TranslateBasicRole(op.AddUserOrgRole.GetRole())
|
||||
tuple := &openfgav1.TupleKey{
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, op.AddUserOrgRole.GetUser(), ""),
|
||||
Relation: zanzana.RelationAssignee,
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeRole, basicRole, ""),
|
||||
}
|
||||
writeTuples = append(writeTuples, tuple)
|
||||
case *authzextv1.MutateOperation_DeleteUserOrgRole:
|
||||
tuple, err := s.getUserOrgRoleDeleteTuple(ctx, store, op.DeleteUserOrgRole)
|
||||
basicRole := zanzana.TranslateBasicRole(op.DeleteUserOrgRole.GetRole())
|
||||
tuple := &openfgav1.TupleKeyWithoutCondition{
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, op.DeleteUserOrgRole.GetUser(), ""),
|
||||
Relation: zanzana.RelationAssignee,
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeRole, basicRole, ""),
|
||||
}
|
||||
deleteTuples = append(deleteTuples, tuple)
|
||||
case *authzextv1.MutateOperation_UpdateUserOrgRole:
|
||||
writeTuple, existingTuples, err := s.getUserOrgRoleUpdateTuples(ctx, store, op.UpdateUserOrgRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleteTuples = append(deleteTuples, tuple)
|
||||
writeTuples = append(writeTuples, writeTuple)
|
||||
deleteTuples = append(deleteTuples, existingTuples...)
|
||||
default:
|
||||
s.logger.Debug("unsupported mutate operation", "operation", op)
|
||||
}
|
||||
@@ -65,20 +76,38 @@ func (s *Server) mutateOrgRoles(ctx context.Context, store *storeInfo, operation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getUserOrgRoleWriteTuple(ctx context.Context, store *storeInfo, req *authzextv1.UpdateUserOrgRoleOperation) (*openfgav1.TupleKey, error) {
|
||||
basicRole := zanzana.TranslateBasicRole(req.GetRole())
|
||||
return &openfgav1.TupleKey{
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, req.GetUser(), ""),
|
||||
Relation: zanzana.RelationAssignee,
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeRole, basicRole, ""),
|
||||
}, nil
|
||||
}
|
||||
func (s *Server) getUserOrgRoleUpdateTuples(ctx context.Context, store *storeInfo, req *authzextv1.UpdateUserOrgRoleOperation) (*openfgav1.TupleKey, []*openfgav1.TupleKeyWithoutCondition, error) {
|
||||
readReq := &openfgav1.ReadRequest{
|
||||
StoreId: store.ID,
|
||||
TupleKey: &openfgav1.ReadRequestTupleKey{
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, req.GetUser(), ""),
|
||||
Relation: zanzana.RelationAssignee,
|
||||
// read tuples by object type ("role:")
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeRole, "", ""),
|
||||
},
|
||||
}
|
||||
res, err := s.openfga.Read(ctx, readReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
existingBasicRoleTuples := make([]*openfgav1.TupleKeyWithoutCondition, 0)
|
||||
for _, tuple := range res.GetTuples() {
|
||||
_, roleName, _ := zanzana.SplitTupleObject(tuple.GetKey().GetObject())
|
||||
if zanzana.IsBasicRole(roleName) {
|
||||
existingBasicRoleTuples = append(existingBasicRoleTuples, &openfgav1.TupleKeyWithoutCondition{
|
||||
User: tuple.GetKey().GetUser(),
|
||||
Relation: tuple.GetKey().GetRelation(),
|
||||
Object: tuple.GetKey().GetObject(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getUserOrgRoleDeleteTuple(ctx context.Context, store *storeInfo, req *authzextv1.DeleteUserOrgRoleOperation) (*openfgav1.TupleKeyWithoutCondition, error) {
|
||||
basicRole := zanzana.TranslateBasicRole(req.GetRole())
|
||||
return &openfgav1.TupleKeyWithoutCondition{
|
||||
writeTuple := &openfgav1.TupleKey{
|
||||
User: zanzana.NewTupleEntry(zanzana.TypeUser, req.GetUser(), ""),
|
||||
Relation: zanzana.RelationAssignee,
|
||||
Object: zanzana.NewTupleEntry(zanzana.TypeRole, basicRole, ""),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return writeTuple, existingBasicRoleTuples, nil
|
||||
}
|
||||
|
||||
@@ -36,14 +36,6 @@ func testMutateOrgRoles(t *testing.T, srv *Server) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Operation: &v1.MutateOperation_DeleteUserOrgRole{
|
||||
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{
|
||||
User: "1",
|
||||
Role: "Editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -69,4 +61,50 @@ func testMutateOrgRoles(t *testing.T, srv *Server) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Tuples, 0)
|
||||
})
|
||||
|
||||
t.Run("should add user org role and delete old role", func(t *testing.T) {
|
||||
_, err := srv.Mutate(newContextWithNamespace(), &v1.MutateRequest{
|
||||
Namespace: "default",
|
||||
Operations: []*v1.MutateOperation{
|
||||
{
|
||||
Operation: &v1.MutateOperation_AddUserOrgRole{
|
||||
AddUserOrgRole: &v1.AddUserOrgRoleOperation{
|
||||
User: "1",
|
||||
Role: "Viewer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Operation: &v1.MutateOperation_DeleteUserOrgRole{
|
||||
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{
|
||||
User: "1",
|
||||
Role: "Admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := srv.Read(newContextWithNamespace(), &v1.ReadRequest{
|
||||
Namespace: "default",
|
||||
TupleKey: &v1.ReadRequestTupleKey{
|
||||
Relation: common.RelationAssignee,
|
||||
Object: "role:basic_admin",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Tuples, 0)
|
||||
|
||||
res, err = srv.Read(newContextWithNamespace(), &v1.ReadRequest{
|
||||
Namespace: "default",
|
||||
TupleKey: &v1.ReadRequestTupleKey{
|
||||
Relation: common.RelationAssignee,
|
||||
Object: "role:basic_viewer",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Tuples, 1)
|
||||
require.Equal(t, "user:1", res.Tuples[0].Key.User)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user