mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-08 23:50:40 +01:00
refactor(authz): split role into domain Role and StorableRole
Replace the Scan/Value/MarshalJSON codecs on TransactionGroups with the storable pattern: StorableRole is the bun model carrying transaction groups as raw JSON text, Role is the pure domain/wire type, and NewStorableRoleFromRole/NewRoleFromStorableRole convert at the store boundary (nil groups persist as [], reads parse through the validating constructor). RoleStore and sqlauthzstore speak StorableRole; both providers convert; handlers and the wire contract are unchanged.
This commit is contained in:
@@ -200,11 +200,21 @@ func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *a
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.store.Create(ctx, role)
|
||||
storableRole, err := authtypes.NewStorableRoleFromRole(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.store.Create(ctx, storableRole)
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
return provider.store.Get(ctx, orgID, id)
|
||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
func (provider *provider) Update(ctx context.Context, orgID valuer.UUID, updatedRole *authtypes.Role) error {
|
||||
@@ -240,7 +250,12 @@ func (provider *provider) Update(ctx context.Context, orgID valuer.UUID, updated
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.store.Update(ctx, orgID, updatedRole)
|
||||
storableRole, err := authtypes.NewStorableRoleFromRole(updatedRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.store.Update(ctx, orgID, storableRole)
|
||||
}
|
||||
|
||||
func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
|
||||
@@ -18,22 +18,22 @@ func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) authtypes.RoleStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, role *authtypes.Role) error {
|
||||
func (store *store) Create(ctx context.Context, storableRole *authtypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(role).
|
||||
Model(storableRole).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "role with name: %s already exists", role.Name)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "role with name: %s already exists", storableRole.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
role := new(authtypes.Role)
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -49,8 +49,8 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
role := new(authtypes.Role)
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -66,8 +66,8 @@ func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, na
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -82,8 +82,8 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.R
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -103,8 +103,8 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -124,17 +124,17 @@ func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, id
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, storableRole *authtypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(role).
|
||||
Model(storableRole).
|
||||
WherePK().
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeAlreadyExists, "role with id %s doesn't exist", role.ID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeAlreadyExists, "role with id %s doesn't exist", storableRole.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -145,7 +145,7 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(authtypes.Role)).
|
||||
Model(new(authtypes.StorableRole)).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
|
||||
@@ -84,11 +84,21 @@ func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.
|
||||
}
|
||||
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
return provider.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
storableRole, err := provider.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
return provider.store.List(ctx, orgID)
|
||||
storableRoles, err := provider.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRolesFromStorableRoles(storableRoles)
|
||||
}
|
||||
|
||||
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
@@ -101,11 +111,21 @@ func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[s
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
return provider.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
storableRoles, err := provider.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRolesFromStorableRoles(storableRoles)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
return provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
storableRoles, err := provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRolesFromStorableRoles(storableRoles)
|
||||
}
|
||||
|
||||
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
|
||||
@@ -157,10 +177,14 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*authtypes.Role) error {
|
||||
err := provider.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
for _, role := range managedRoles {
|
||||
err := provider.store.Create(ctx, role)
|
||||
storableRole, err := authtypes.NewStorableRoleFromRole(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.store.Create(ctx, storableRole); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -62,15 +62,25 @@ var (
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
Name string `json:"name" required:"true"`
|
||||
Description string `json:"description" required:"true"`
|
||||
Type valuer.String `json:"type" required:"true"`
|
||||
OrgID valuer.UUID `json:"orgId" required:"true"`
|
||||
TransactionGroups TransactionGroups `json:"transactionGroups" required:"true" nullable:"false"`
|
||||
}
|
||||
|
||||
type StorableRole struct {
|
||||
bun.BaseModel `bun:"table:role"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
Name string `bun:"name,type:string" json:"name" required:"true"`
|
||||
Description string `bun:"description,type:string" json:"description" required:"true"`
|
||||
Type valuer.String `bun:"type,type:string" json:"type" required:"true"`
|
||||
OrgID valuer.UUID `bun:"org_id,type:string" json:"orgId" required:"true"`
|
||||
TransactionGroups TransactionGroups `bun:"transaction_groups,type:text" json:"transactionGroups" required:"true" nullable:"false"`
|
||||
Name string `bun:"name,type:string"`
|
||||
Description string `bun:"description,type:string"`
|
||||
Type valuer.String `bun:"type,type:string"`
|
||||
OrgID valuer.UUID `bun:"org_id,type:string"`
|
||||
TransactionGroups string `bun:"transaction_groups,type:text,nullzero"`
|
||||
}
|
||||
|
||||
type GettableRole struct {
|
||||
@@ -110,6 +120,62 @@ func NewRole(name, description string, roleType valuer.String, orgID valuer.UUID
|
||||
}
|
||||
}
|
||||
|
||||
func NewStorableRoleFromRole(role *Role) (*StorableRole, error) {
|
||||
transactionGroups := role.TransactionGroups
|
||||
if transactionGroups == nil {
|
||||
transactionGroups = make(TransactionGroups, 0)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(transactionGroups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StorableRole{
|
||||
Identifiable: role.Identifiable,
|
||||
TimeAuditable: role.TimeAuditable,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
Type: role.Type,
|
||||
OrgID: role.OrgID,
|
||||
TransactionGroups: string(data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRoleFromStorableRole(storableRole *StorableRole) (*Role, error) {
|
||||
transactionGroups := make(TransactionGroups, 0)
|
||||
if storableRole.TransactionGroups != "" {
|
||||
var err error
|
||||
transactionGroups, err = NewTransactionGroups([]byte(storableRole.TransactionGroups))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Role{
|
||||
Identifiable: storableRole.Identifiable,
|
||||
TimeAuditable: storableRole.TimeAuditable,
|
||||
Name: storableRole.Name,
|
||||
Description: storableRole.Description,
|
||||
Type: storableRole.Type,
|
||||
OrgID: storableRole.OrgID,
|
||||
TransactionGroups: transactionGroups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRolesFromStorableRoles(storableRoles []*StorableRole) ([]*Role, error) {
|
||||
roles := make([]*Role, len(storableRoles))
|
||||
for index, storableRole := range storableRoles {
|
||||
role, err := NewRoleFromStorableRole(storableRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles[index] = role
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func NewGettableRolesFromRoles(roles []*Role) []*GettableRole {
|
||||
gettableRoles := make([]*GettableRole, len(roles))
|
||||
for index, role := range roles {
|
||||
@@ -259,13 +325,13 @@ func NormalizeRoleName(role string) string {
|
||||
}
|
||||
|
||||
type RoleStore interface {
|
||||
Create(context.Context, *Role) error
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*Role, error)
|
||||
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*Role, error)
|
||||
List(context.Context, valuer.UUID) ([]*Role, error)
|
||||
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*Role, error)
|
||||
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*Role, error)
|
||||
Update(context.Context, valuer.UUID, *Role) error
|
||||
Create(context.Context, *StorableRole) error
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableRole, error)
|
||||
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*StorableRole, error)
|
||||
List(context.Context, valuer.UUID) ([]*StorableRole, error)
|
||||
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*StorableRole, error)
|
||||
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*StorableRole, error)
|
||||
Update(context.Context, valuer.UUID, *StorableRole) error
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
RunInTx(context.Context, func(ctx context.Context) error) error
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -110,48 +109,6 @@ func (groups TransactionGroups) Diff(desired TransactionGroups) (additions, dele
|
||||
return desired.subtract(groups), groups.subtract(desired)
|
||||
}
|
||||
|
||||
func (groups TransactionGroups) MarshalJSON() ([]byte, error) {
|
||||
if groups == nil {
|
||||
return []byte("[]"), nil
|
||||
}
|
||||
|
||||
return json.Marshal([]*TransactionGroup(groups))
|
||||
}
|
||||
|
||||
func (groups TransactionGroups) Value() (driver.Value, error) {
|
||||
data, err := json.Marshal(groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (groups *TransactionGroups) Scan(value any) error {
|
||||
if value == nil {
|
||||
*groups = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
data = []byte(typed)
|
||||
case []byte:
|
||||
data = typed
|
||||
default:
|
||||
return errors.Newf(errors.TypeInternal, errors.CodeInternal, "unsupported type %T for transaction groups", value)
|
||||
}
|
||||
|
||||
parsed, err := NewTransactionGroups(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*groups = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transaction *Transaction) UnmarshalJSON(data []byte) error {
|
||||
var shadow = struct {
|
||||
Relation Relation
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/coretypes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTransactionGroupsValueScanRoundTrip(t *testing.T) {
|
||||
input := []byte(`[
|
||||
{"relation":"read","objectGroup":{"resource":{"type":"metaresource","kind":"dashboard"},"selectors":["*"]}},
|
||||
{"relation":"update","objectGroup":{"resource":{"type":"metaresource","kind":"dashboard"},"selectors":["019823cf-c360-7d0a-9a3f-a37656161ff9"]}}
|
||||
]`)
|
||||
groups, err := NewTransactionGroups(input)
|
||||
require.NoError(t, err)
|
||||
|
||||
value, err := groups.Value()
|
||||
require.NoError(t, err)
|
||||
|
||||
var scanned TransactionGroups
|
||||
require.NoError(t, scanned.Scan(value))
|
||||
assert.Equal(t, groups, scanned)
|
||||
}
|
||||
|
||||
func TestTransactionGroupsValueNil(t *testing.T) {
|
||||
var groups TransactionGroups
|
||||
value, err := groups.Value()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "[]", value)
|
||||
}
|
||||
|
||||
func TestTransactionGroupsScan(t *testing.T) {
|
||||
var groups TransactionGroups
|
||||
require.NoError(t, groups.Scan(nil))
|
||||
assert.Nil(t, groups)
|
||||
|
||||
require.NoError(t, groups.Scan([]byte(`[]`)))
|
||||
assert.Empty(t, groups)
|
||||
|
||||
assert.Error(t, groups.Scan("not json"))
|
||||
assert.Error(t, groups.Scan(42))
|
||||
assert.Error(t, groups.Scan(`[{"relation":"fly","objectGroup":{"resource":{"type":"metaresource","kind":"dashboard"},"selectors":["*"]}}]`))
|
||||
}
|
||||
|
||||
func TestNewTransactionGroupsFromTransactions(t *testing.T) {
|
||||
dashboard := coretypes.ResourceRef{Type: coretypes.TypeMetaResource, Kind: coretypes.KindDashboard}
|
||||
rule := coretypes.ResourceRef{Type: coretypes.TypeMetaResource, Kind: coretypes.KindRule}
|
||||
|
||||
transactions := []coretypes.Transaction{
|
||||
{Verb: coretypes.VerbUpdate, Object: *coretypes.MustNewObject(dashboard, coretypes.WildCardSelectorString)},
|
||||
{Verb: coretypes.VerbRead, Object: *coretypes.MustNewObject(dashboard, coretypes.WildCardSelectorString)},
|
||||
{Verb: coretypes.VerbRead, Object: *coretypes.MustNewObject(rule, coretypes.WildCardSelectorString)},
|
||||
}
|
||||
|
||||
groups := NewTransactionGroupsFromTransactions(transactions)
|
||||
require.Len(t, groups, 3)
|
||||
|
||||
for _, group := range groups {
|
||||
require.Len(t, group.ObjectGroup.Selectors, 1)
|
||||
assert.Equal(t, coretypes.WildCardSelectorString, group.ObjectGroup.Selectors[0].String())
|
||||
}
|
||||
|
||||
assert.Equal(t, coretypes.VerbRead, groups[0].Relation.Verb)
|
||||
assert.Equal(t, dashboard, groups[0].ObjectGroup.Resource)
|
||||
assert.Equal(t, coretypes.VerbRead, groups[1].Relation.Verb)
|
||||
assert.Equal(t, rule, groups[1].ObjectGroup.Resource)
|
||||
assert.Equal(t, coretypes.VerbUpdate, groups[2].Relation.Verb)
|
||||
assert.Equal(t, dashboard, groups[2].ObjectGroup.Resource)
|
||||
}
|
||||
Reference in New Issue
Block a user