From 245179cbf7eba429740e5d984d4d2a81361b7e8f Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Fri, 14 Nov 2025 00:43:02 +0530 Subject: [PATCH] feat(authz): openfga sql migration (#9580) * feat(authz): openfga sql migration * feat(authz): formatting and naming * feat(authz): formatting and naming * feat(authz): extract function for store and model id * feat(authz): reorder the provider --- ee/authz/openfgaschema/base.fga | 6 +- pkg/authz/openfgaauthz/provider.go | 236 ++++++++++-------- pkg/modules/role/implrole/handler.go | 53 +--- pkg/modules/role/implrole/module.go | 4 +- pkg/modules/role/implrole/store.go | 8 +- pkg/signoz/provider.go | 1 + pkg/sqlmigration/051_add_authz.go | 145 +++++++++++ pkg/types/authtypes/name.go | 7 + pkg/types/authtypes/relation.go | 10 +- pkg/types/authtypes/selector.go | 80 +++--- pkg/types/authtypes/transaction.go | 38 +-- pkg/types/authtypes/typeable.go | 35 +-- pkg/types/authtypes/typeable_metaresource.go | 48 ++++ pkg/types/authtypes/typeable_metaresources.go | 48 ++++ pkg/types/authtypes/typeable_resource.go | 47 ---- pkg/types/authtypes/typeable_resources.go | 47 ---- pkg/types/authtypes/typeable_role.go | 2 +- pkg/types/authtypes/typeable_user.go | 2 +- pkg/types/dashboardtypes/dashboard.go | 4 +- pkg/types/roletypes/role.go | 2 +- pkg/valuer/email.go | 4 + pkg/valuer/string.go | 4 + pkg/valuer/uuid.go | 4 + pkg/valuer/valuer.go | 3 + 24 files changed, 507 insertions(+), 331 deletions(-) create mode 100644 pkg/sqlmigration/051_add_authz.go create mode 100644 pkg/types/authtypes/typeable_metaresource.go create mode 100644 pkg/types/authtypes/typeable_metaresources.go delete mode 100644 pkg/types/authtypes/typeable_resource.go delete mode 100644 pkg/types/authtypes/typeable_resources.go diff --git a/ee/authz/openfgaschema/base.fga b/ee/authz/openfgaschema/base.fga index e2f1f003d4..fc4d077a4e 100644 --- a/ee/authz/openfgaschema/base.fga +++ b/ee/authz/openfgaschema/base.fga @@ -21,12 +21,12 @@ type role define update: [user, role#assignee] define delete: [user, role#assignee] -type resources +type metaresources relations define create: [user, role#assignee] define list: [user, role#assignee] -type resource +type metaresource relations define read: [user, anonymous, role#assignee] define update: [user, role#assignee] @@ -35,6 +35,6 @@ type resource define block: [user, role#assignee] -type telemetry +type telemetryresource relations define read: [user, anonymous, role#assignee] diff --git a/pkg/authz/openfgaauthz/provider.go b/pkg/authz/openfgaauthz/provider.go index 804975dab4..fa0ddd57b8 100644 --- a/pkg/authz/openfgaauthz/provider.go +++ b/pkg/authz/openfgaauthz/provider.go @@ -94,6 +94,130 @@ func (provider *provider) Stop(ctx context.Context) error { return nil } +func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKey) error { + storeID, modelID := provider.getStoreIDandModelID() + checkResponse, err := provider.openfgaServer.Check( + ctx, + &openfgav1.CheckRequest{ + StoreId: storeID, + AuthorizationModelId: modelID, + TupleKey: &openfgav1.CheckRequestTupleKey{ + User: tupleReq.User, + Relation: tupleReq.Relation, + Object: tupleReq.Object, + }, + }) + if err != nil { + return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error()) + } + + if !checkResponse.Allowed { + return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subject %s cannot %s object %s", tupleReq.User, tupleReq.Relation, tupleReq.Object) + } + + return nil +} + +func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.TupleKey) error { + storeID, modelID := provider.getStoreIDandModelID() + batchCheckItems := make([]*openfgav1.BatchCheckItem, 0) + for _, tuple := range tupleReq { + batchCheckItems = append(batchCheckItems, &openfgav1.BatchCheckItem{ + TupleKey: &openfgav1.CheckRequestTupleKey{ + User: tuple.User, + Relation: tuple.Relation, + Object: tuple.Object, + }, + }) + } + + checkResponse, err := provider.openfgaServer.BatchCheck( + ctx, + &openfgav1.BatchCheckRequest{ + StoreId: storeID, + AuthorizationModelId: modelID, + Checks: batchCheckItems, + }) + if err != nil { + return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error()) + } + + for _, checkResponse := range checkResponse.Result { + if checkResponse.GetAllowed() { + return nil + } + } + + return errors.New(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "") + +} + +func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error { + subject, err := authtypes.NewSubject(authtypes.TypeUser, claims.UserID, authtypes.Relation{}) + if err != nil { + return err + } + + tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID) + if err != nil { + return err + } + + err = provider.BatchCheck(ctx, tuples) + if err != nil { + return err + } + + return nil +} + +func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error { + storeID, modelID := provider.getStoreIDandModelID() + deletionTuplesWithoutCondition := make([]*openfgav1.TupleKeyWithoutCondition, len(deletions)) + for idx, tuple := range deletions { + deletionTuplesWithoutCondition[idx] = &openfgav1.TupleKeyWithoutCondition{User: tuple.User, Object: tuple.Object, Relation: tuple.Relation} + } + + _, err := provider.openfgaServer.Write(ctx, &openfgav1.WriteRequest{ + StoreId: storeID, + AuthorizationModelId: modelID, + Writes: func() *openfgav1.WriteRequestWrites { + if len(additions) == 0 { + return nil + } + return &openfgav1.WriteRequestWrites{ + TupleKeys: additions, + } + }(), + Deletes: func() *openfgav1.WriteRequestDeletes { + if len(deletionTuplesWithoutCondition) == 0 { + return nil + } + return &openfgav1.WriteRequestDeletes{ + TupleKeys: deletionTuplesWithoutCondition, + } + }(), + }) + + return err +} + +func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) { + storeID, modelID := provider.getStoreIDandModelID() + response, err := provider.openfgaServer.ListObjects(ctx, &openfgav1.ListObjectsRequest{ + StoreId: storeID, + AuthorizationModelId: modelID, + User: subject, + Relation: relation.StringValue(), + Type: typeable.Type().StringValue(), + }) + if err != nil { + return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), typeable.Type().StringValue()) + } + + return authtypes.MustNewObjectsFromStringSlice(response.Objects), nil +} + func (provider *provider) getOrCreateStore(ctx context.Context, name string) (string, error) { stores, err := provider.openfgaServer.ListStores(ctx, &openfgav1.ListStoresRequest{}) if err != nil { @@ -176,112 +300,12 @@ func (provider *provider) isModelEqual(expected *openfgav1.AuthorizationModel, a } -func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKey) error { - checkResponse, err := provider.openfgaServer.Check( - ctx, - &openfgav1.CheckRequest{ - StoreId: provider.storeID, - AuthorizationModelId: provider.modelID, - TupleKey: &openfgav1.CheckRequestTupleKey{ - User: tupleReq.User, - Relation: tupleReq.Relation, - Object: tupleReq.Object, - }, - }) - if err != nil { - return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error()) - } +func (provider *provider) getStoreIDandModelID() (string, string) { + provider.mtx.RLock() + defer provider.mtx.RUnlock() - if !checkResponse.Allowed { - return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "subject %s cannot %s object %s", tupleReq.User, tupleReq.Relation, tupleReq.Object) - } + storeID := provider.storeID + modelID := provider.modelID - return nil -} - -func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.TupleKey) error { - batchCheckItems := make([]*openfgav1.BatchCheckItem, 0) - for _, tuple := range tupleReq { - batchCheckItems = append(batchCheckItems, &openfgav1.BatchCheckItem{ - TupleKey: &openfgav1.CheckRequestTupleKey{ - User: tuple.User, - Relation: tuple.Relation, - Object: tuple.Object, - }, - }) - } - - checkResponse, err := provider.openfgaServer.BatchCheck( - ctx, - &openfgav1.BatchCheckRequest{ - StoreId: provider.storeID, - AuthorizationModelId: provider.modelID, - Checks: batchCheckItems, - }) - if err != nil { - return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error()) - } - - for _, checkResponse := range checkResponse.Result { - if checkResponse.GetAllowed() { - return nil - } - } - - return errors.New(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "") - -} - -func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error { - subject, err := authtypes.NewSubject(authtypes.TypeUser, claims.UserID, authtypes.Relation{}) - if err != nil { - return err - } - - tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID) - if err != nil { - return err - } - - err = provider.BatchCheck(ctx, tuples) - if err != nil { - return err - } - - return nil -} - -func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error { - deletionTuplesWithoutCondition := make([]*openfgav1.TupleKeyWithoutCondition, len(deletions)) - for idx, tuple := range deletions { - deletionTuplesWithoutCondition[idx] = &openfgav1.TupleKeyWithoutCondition{User: tuple.User, Object: tuple.Object, Relation: tuple.Relation} - } - - _, err := provider.openfgaServer.Write(ctx, &openfgav1.WriteRequest{ - StoreId: provider.storeID, - AuthorizationModelId: provider.modelID, - Writes: &openfgav1.WriteRequestWrites{ - TupleKeys: additions, - }, - Deletes: &openfgav1.WriteRequestDeletes{ - TupleKeys: deletionTuplesWithoutCondition, - }, - }) - - return err -} - -func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) { - response, err := provider.openfgaServer.ListObjects(ctx, &openfgav1.ListObjectsRequest{ - StoreId: provider.storeID, - AuthorizationModelId: provider.modelID, - User: subject, - Relation: relation.StringValue(), - Type: typeable.Type().StringValue(), - }) - if err != nil { - return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), typeable.Type().StringValue()) - } - - return authtypes.MustNewObjectsFromStringSlice(response.Objects), nil + return storeID, modelID } diff --git a/pkg/modules/role/implrole/handler.go b/pkg/modules/role/implrole/handler.go index a2d43b0715..c0a557f1fa 100644 --- a/pkg/modules/role/implrole/handler.go +++ b/pkg/modules/role/implrole/handler.go @@ -17,8 +17,8 @@ type handler struct { module role.Module } -func NewHandler(module role.Module) (role.Handler, error) { - return &handler{module: module}, nil +func NewHandler(module role.Module) role.Handler { + return &handler{module: module} } func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) { @@ -28,11 +28,6 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } req := new(roletypes.PostableRole) if err := binding.JSON.BindBody(r.Body, req); err != nil { @@ -40,7 +35,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) { return } - role, err := handler.module.Create(ctx, orgID, req.DisplayName, req.Description) + role, err := handler.module.Create(ctx, valuer.MustNewUUID(claims.OrgID), req.DisplayName, req.Description) if err != nil { render.Error(rw, err) return @@ -56,11 +51,6 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } id, ok := mux.Vars(r)["id"] if !ok { @@ -73,7 +63,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) { return } - role, err := handler.module.Get(ctx, orgID, roleID) + role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), roleID) if err != nil { render.Error(rw, err) return @@ -89,11 +79,6 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } id, ok := mux.Vars(r)["id"] if !ok { @@ -117,7 +102,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) { return } - objects, err := handler.module.GetObjects(ctx, orgID, roleID, relation) + objects, err := handler.module.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation) if err != nil { render.Error(rw, err) return @@ -147,13 +132,8 @@ func (handler *handler) List(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } - roles, err := handler.module.List(ctx, orgID) + roles, err := handler.module.List(ctx, valuer.MustNewUUID(claims.OrgID)) if err != nil { render.Error(rw, err) return @@ -169,11 +149,6 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } id, ok := mux.Vars(r)["id"] if !ok { @@ -192,7 +167,7 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) { return } - err = handler.module.Patch(ctx, orgID, roleID, req.DisplayName, req.Description) + err = handler.module.Patch(ctx, valuer.MustNewUUID(claims.OrgID), roleID, req.DisplayName, req.Description) if err != nil { render.Error(rw, err) return @@ -208,11 +183,6 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } id, ok := mux.Vars(r)["id"] if !ok { @@ -248,7 +218,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) { return } - err = handler.module.PatchObjects(ctx, orgID, roleID, relation, patchableObjects.Additions, patchableObjects.Deletions) + err = handler.module.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation, patchableObjects.Additions, patchableObjects.Deletions) if err != nil { render.Error(rw, err) return @@ -264,11 +234,6 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) { render.Error(rw, err) return } - orgID, err := valuer.NewUUID(claims.OrgID) - if err != nil { - render.Error(rw, err) - return - } id, ok := mux.Vars(r)["id"] if !ok { @@ -281,7 +246,7 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) { return } - err = handler.module.Delete(ctx, orgID, roleID) + err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), roleID) if err != nil { render.Error(rw, err) return diff --git a/pkg/modules/role/implrole/module.go b/pkg/modules/role/implrole/module.go index cfda2a08ed..e58cbe2c8b 100644 --- a/pkg/modules/role/implrole/module.go +++ b/pkg/modules/role/implrole/module.go @@ -17,12 +17,12 @@ type module struct { authz authz.AuthZ } -func NewModule(ctx context.Context, store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) (role.Module, error) { +func NewModule(store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) role.Module { return &module{ store: store, authz: authz, registry: registry, - }, nil + } } func (module *module) Create(ctx context.Context, orgID valuer.UUID, displayName, description string) (*roletypes.Role, error) { diff --git a/pkg/modules/role/implrole/store.go b/pkg/modules/role/implrole/store.go index 65a1c79b8b..01f18a7440 100644 --- a/pkg/modules/role/implrole/store.go +++ b/pkg/modules/role/implrole/store.go @@ -13,8 +13,8 @@ type store struct { sqlstore sqlstore.SQLStore } -func NewStore(sqlstore sqlstore.SQLStore) (roletypes.Store, error) { - return &store{sqlstore: sqlstore}, nil +func NewStore(sqlstore sqlstore.SQLStore) roletypes.Store { + return &store{sqlstore: sqlstore} } func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error { @@ -38,7 +38,7 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) BunDB(). NewSelect(). Model(role). - Where("orgID = ?", orgID). + Where("org_id = ?", orgID). Where("id = ?", id). Scan(ctx) if err != nil { @@ -55,7 +55,7 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S BunDB(). NewSelect(). Model(&roles). - Where("orgID = ?", orgID). + Where("org_id = ?", orgID). Scan(ctx) if err != nil { return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "no roles found in org_id: %s", orgID) diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 740eb83595..a42de0411f 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -138,6 +138,7 @@ func NewSQLMigrationProviderFactories( sqlmigration.NewUpdateTTLSettingForCustomRetentionFactory(sqlstore, sqlschema), sqlmigration.NewAddRoutePolicyFactory(sqlstore, sqlschema), sqlmigration.NewAddAuthTokenFactory(sqlstore, sqlschema), + sqlmigration.NewAddAuthzFactory(sqlstore, sqlschema), ) } diff --git a/pkg/sqlmigration/051_add_authz.go b/pkg/sqlmigration/051_add_authz.go new file mode 100644 index 0000000000..58f67486f5 --- /dev/null +++ b/pkg/sqlmigration/051_add_authz.go @@ -0,0 +1,145 @@ +package sqlmigration + +import ( + "context" + + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/sqlschema" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +type addAuthz struct { + sqlstore sqlstore.SQLStore + sqlschema sqlschema.SQLSchema +} + +func NewAddAuthzFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] { + return factory.NewProviderFactory(factory.MustNewName("add_authz"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { + return newAddAuthz(ctx, ps, c, sqlstore, sqlschema) + }) +} + +func newAddAuthz(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) { + return &addAuthz{ + sqlstore: sqlstore, + sqlschema: sqlschema, + }, nil +} + +func (migration *addAuthz) Register(migrations *migrate.Migrations) error { + if err := migrations.Register(migration.Up, migration.Down); err != nil { + return err + } + + return nil +} + +func (migration *addAuthz) Up(ctx context.Context, db *bun.DB) error { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + + defer func() { + _ = tx.Rollback() + }() + + sqls := [][]byte{} + tableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{ + Name: "tuple", + Columns: []*sqlschema.Column{ + {Name: "store", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "object_type", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "object_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "relation", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_object_type", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_object_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_relation", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_type", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "ulid", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "inserted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false}, + {Name: "condition_name", DataType: sqlschema.DataTypeText, Nullable: true}, + {Name: "condition_context", DataType: sqlschema.DataTypeText, Nullable: true}, + }, + PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"store", "object_type", "object_id", "relation", "user_object_type", "user_object_id", "user_relation"}}, + }) + sqls = append(sqls, tableSQLs...) + + indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "tuple", ColumnNames: []sqlschema.ColumnName{"ulid"}}) + sqls = append(sqls, indexSQLs...) + + tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{ + Name: "authorization_model", + Columns: []*sqlschema.Column{ + {Name: "store", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "authorization_model_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "schema_version", DataType: sqlschema.DataTypeText, Nullable: false, Default: "1.1"}, + {Name: "serialized_protobuf", DataType: sqlschema.DataTypeText, Nullable: false}, + }, + PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"store", "authorization_model_id"}}, + }) + sqls = append(sqls, tableSQLs...) + + tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{ + Name: "store", + Columns: []*sqlschema.Column{ + {Name: "id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "name", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false}, + {Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: true}, + {Name: "deleted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: true}, + }, + PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"id"}}, + }) + sqls = append(sqls, tableSQLs...) + + tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{ + Name: "assertion", + Columns: []*sqlschema.Column{ + {Name: "store", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "authorization_model_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "assertions", DataType: sqlschema.DataTypeText, Nullable: true}, + }, + PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"store", "authorization_model_id"}}, + }) + sqls = append(sqls, tableSQLs...) + + tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{ + Name: "changelog", + Columns: []*sqlschema.Column{ + {Name: "store", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "object_type", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "object_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "relation", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_object_type", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_object_id", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "user_relation", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "operation", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "ulid", DataType: sqlschema.DataTypeText, Nullable: false}, + {Name: "inserted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false}, + {Name: "condition_name", DataType: sqlschema.DataTypeText, Nullable: true}, + {Name: "condition_context", DataType: sqlschema.DataTypeText, Nullable: true}, + }, + PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"store", "ulid", "object_type"}}, + }) + sqls = append(sqls, tableSQLs...) + + for _, sql := range sqls { + if _, err := tx.ExecContext(ctx, string(sql)); err != nil { + return err + } + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil +} + +func (migration *addAuthz) Down(context.Context, *bun.DB) error { + return nil +} diff --git a/pkg/types/authtypes/name.go b/pkg/types/authtypes/name.go index e22e21345e..aa256e208c 100644 --- a/pkg/types/authtypes/name.go +++ b/pkg/types/authtypes/name.go @@ -9,6 +9,9 @@ import ( var ( nameRegex = regexp.MustCompile("^[a-z]{1,35}$") + + _ json.Marshaler = new(Name) + _ json.Unmarshaler = new(Name) ) type Name struct { @@ -36,6 +39,10 @@ func (name Name) String() string { return name.val } +func (name *Name) MarshalJSON() ([]byte, error) { + return json.Marshal(name.val) +} + func (name *Name) UnmarshalJSON(data []byte) error { nameStr := "" err := json.Unmarshal(data, &nameStr) diff --git a/pkg/types/authtypes/relation.go b/pkg/types/authtypes/relation.go index 165b888e75..e2d5a9568d 100644 --- a/pkg/types/authtypes/relation.go +++ b/pkg/types/authtypes/relation.go @@ -20,11 +20,11 @@ var ( ) var TypeableRelations = map[Type][]Relation{ - TypeUser: {RelationRead, RelationUpdate, RelationDelete}, - TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete}, - TypeOrganization: {RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList}, - TypeResource: {RelationRead, RelationUpdate, RelationDelete, RelationBlock}, - TypeResources: {RelationCreate, RelationList}, + TypeUser: {RelationRead, RelationUpdate, RelationDelete}, + TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete}, + TypeOrganization: {RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList}, + TypeMetaResource: {RelationRead, RelationUpdate, RelationDelete, RelationBlock}, + TypeMetaResources: {RelationCreate, RelationList}, } type Relation struct{ valuer.String } diff --git a/pkg/types/authtypes/selector.go b/pkg/types/authtypes/selector.go index ca357499d7..e374b8928e 100644 --- a/pkg/types/authtypes/selector.go +++ b/pkg/types/authtypes/selector.go @@ -9,16 +9,21 @@ import ( ) var ( - ErrCodeAuthZInvalidSelectorRegex = errors.MustNewCode("authz_invalid_selector_regex") + ErrCodeAuthZInvalidSelector = errors.MustNewCode("authz_invalid_selector") +) + +var ( + _ json.Marshaler = new(Selector) + _ json.Unmarshaler = new(Selector) ) var ( typeUserSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) typeRoleSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) typeOrganizationSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) - typeResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) - // resources selectors are used to select either all or none - typeResourcesSelectorRegex = regexp.MustCompile(`^\*$`) + typeMetaResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) + // metaresources selectors are used to select either all or none + typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`) ) type SelectorCallbackFn func(context.Context, Claims) ([]Selector, error) @@ -36,33 +41,6 @@ func NewSelector(typed Type, selector string) (Selector, error) { return Selector{val: selector}, nil } -func IsValidSelector(typed Type, selector string) error { - switch typed { - case TypeUser: - if !typeUserSelectorRegex.MatchString(selector) { - return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeUserSelectorRegex.String()) - } - case TypeRole: - if !typeRoleSelectorRegex.MatchString(selector) { - return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeRoleSelectorRegex.String()) - } - case TypeOrganization: - if !typeOrganizationSelectorRegex.MatchString(selector) { - return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeOrganizationSelectorRegex.String()) - } - case TypeResource: - if !typeResourceSelectorRegex.MatchString(selector) { - return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourceSelectorRegex.String()) - } - case TypeResources: - if !typeResourcesSelectorRegex.MatchString(selector) { - return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourcesSelectorRegex.String()) - } - } - - return nil -} - func MustNewSelector(typed Type, input string) Selector { selector, err := NewSelector(typed, input) if err != nil { @@ -72,6 +50,10 @@ func MustNewSelector(typed Type, input string) Selector { return selector } +func (selector *Selector) MarshalJSON() ([]byte, error) { + return json.Marshal(selector.val) +} + func (selector Selector) String() string { return selector.val } @@ -83,8 +65,40 @@ func (typed *Selector) UnmarshalJSON(data []byte) error { return err } - shadow := Selector{val: str} - *typed = shadow + alias := Selector{val: str} + *typed = alias return nil } + +func IsValidSelector(typed Type, selector string) error { + switch typed { + case TypeUser: + if !typeUserSelectorRegex.MatchString(selector) { + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeUserSelectorRegex.String()) + } + return nil + case TypeRole: + if !typeRoleSelectorRegex.MatchString(selector) { + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeRoleSelectorRegex.String()) + } + return nil + case TypeOrganization: + if !typeOrganizationSelectorRegex.MatchString(selector) { + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeOrganizationSelectorRegex.String()) + } + return nil + case TypeMetaResource: + if !typeMetaResourceSelectorRegex.MatchString(selector) { + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourceSelectorRegex.String()) + } + return nil + case TypeMetaResources: + if !typeMetaResourcesSelectorRegex.MatchString(selector) { + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourcesSelectorRegex.String()) + } + return nil + } + + return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", typed) +} diff --git a/pkg/types/authtypes/transaction.go b/pkg/types/authtypes/transaction.go index 7d473a108c..73d45dc9bf 100644 --- a/pkg/types/authtypes/transaction.go +++ b/pkg/types/authtypes/transaction.go @@ -33,22 +33,24 @@ func NewObject(resource Resource, selector Selector) (*Object, error) { } func MustNewObjectFromString(input string) *Object { - parts := strings.Split(input, ":") - if len(parts) != 3 { - panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid list objects output: %s", input)) + parts := strings.Split(input, "/") + if len(parts) != 4 { + panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid input format: %s", input)) + } + + typeParts := strings.Split(parts[0], ":") + if len(typeParts) != 2 { + panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid type format: %s", parts[0])) } resource := Resource{ - Type: MustNewType(parts[0]), - Name: MustNewName(parts[1]), + Type: MustNewType(typeParts[0]), + Name: MustNewName(parts[2]), } - object := &Object{ - Resource: resource, - Selector: MustNewSelector(resource.Type, parts[2]), - } + selector := MustNewSelector(resource.Type, parts[3]) - return object + return &Object{Resource: resource, Selector: selector} } func MustNewObjectsFromStringSlice(input []string) []*Object { @@ -59,6 +61,14 @@ func MustNewObjectsFromStringSlice(input []string) []*Object { return objects } +func NewTransaction(relation Relation, object Object) (*Transaction, error) { + if !slices.Contains(TypeableRelations[object.Resource.Type], relation) { + return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue()) + } + + return &Transaction{Relation: relation, Object: object}, nil +} + func (object *Object) UnmarshalJSON(data []byte) error { var shadow = struct { Resource Resource @@ -79,14 +89,6 @@ func (object *Object) UnmarshalJSON(data []byte) error { return nil } -func NewTransaction(relation Relation, object Object) (*Transaction, error) { - if !slices.Contains(TypeableRelations[object.Resource.Type], relation) { - return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue()) - } - - return &Transaction{Relation: relation, Object: object}, nil -} - func (transaction *Transaction) UnmarshalJSON(data []byte) error { var shadow = struct { Relation Relation diff --git a/pkg/types/authtypes/typeable.go b/pkg/types/authtypes/typeable.go index 2eeb50fd3f..f23bc2b154 100644 --- a/pkg/types/authtypes/typeable.go +++ b/pkg/types/authtypes/typeable.go @@ -11,14 +11,15 @@ import ( var ( ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable") ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden") + ErrCodeAuthZInvalidType = errors.MustNewCode("authz_invalid_type") ) var ( - TypeUser = Type{valuer.NewString("user")} - TypeRole = Type{valuer.NewString("role")} - TypeOrganization = Type{valuer.NewString("organization")} - TypeResource = Type{valuer.NewString("resource")} - TypeResources = Type{valuer.NewString("resources")} + TypeUser = Type{valuer.NewString("user")} + TypeRole = Type{valuer.NewString("role")} + TypeOrganization = Type{valuer.NewString("organization")} + TypeMetaResource = Type{valuer.NewString("metaresource")} + TypeMetaResources = Type{valuer.NewString("metaresources")} ) var ( @@ -53,12 +54,12 @@ func NewType(input string) (Type, error) { return TypeRole, nil case "organization": return TypeOrganization, nil - case "resource": - return TypeResource, nil - case "resources": - return TypeResources, nil + case "metaresource": + return TypeMetaResource, nil + case "metaresources": + return TypeMetaResources, nil default: - return Type{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid type: %s", input) + return Type{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", input) } } @@ -69,12 +70,12 @@ func (typed *Type) UnmarshalJSON(data []byte) error { return err } - shadow, err := NewType(str) + alias, err := NewType(str) if err != nil { return err } - *typed = shadow + *typed = alias return nil } @@ -86,21 +87,21 @@ func NewTypeableFromType(typed Type, name Name) (Typeable, error) { return TypeableUser, nil case TypeOrganization: return TypeableOrganization, nil - case TypeResource: - resource, err := NewTypeableResource(name) + case TypeMetaResource: + resource, err := NewTypeableMetaResource(name) if err != nil { return nil, err } return resource, nil - case TypeResources: - resources, err := NewTypeableResources(name) + case TypeMetaResources: + resources, err := NewTypeableMetaResources(name) if err != nil { return nil, err } return resources, nil } - return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid type") + return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type %s", typed) } func MustNewTypeableFromType(typed Type, name Name) Typeable { diff --git a/pkg/types/authtypes/typeable_metaresource.go b/pkg/types/authtypes/typeable_metaresource.go new file mode 100644 index 0000000000..f7f1015df2 --- /dev/null +++ b/pkg/types/authtypes/typeable_metaresource.go @@ -0,0 +1,48 @@ +package authtypes + +import ( + "github.com/SigNoz/signoz/pkg/valuer" + openfgav1 "github.com/openfga/api/proto/openfga/v1" +) + +var _ Typeable = new(typeableMetaResource) + +type typeableMetaResource struct { + name Name +} + +func NewTypeableMetaResource(name Name) (Typeable, error) { + return &typeableMetaResource{name: name}, nil +} + +func MustNewTypeableMetaResource(name Name) Typeable { + typeableesource, err := NewTypeableMetaResource(name) + if err != nil { + panic(err) + } + + return typeableesource +} + +func (typeableMetaResource *typeableMetaResource) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) { + tuples := make([]*openfgav1.TupleKey, 0) + for _, selector := range selector { + object := typeableMetaResource.Prefix(orgID) + "/" + selector.String() + tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object}) + } + + return tuples, nil +} + +func (typeableMetaResource *typeableMetaResource) Type() Type { + return TypeMetaResource +} + +func (typeableMetaResource *typeableMetaResource) Name() Name { + return typeableMetaResource.name +} + +// example: metaresource:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboard +func (typeableMetaResource *typeableMetaResource) Prefix(orgID valuer.UUID) string { + return typeableMetaResource.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResource.Name().String() +} diff --git a/pkg/types/authtypes/typeable_metaresources.go b/pkg/types/authtypes/typeable_metaresources.go new file mode 100644 index 0000000000..68c2427322 --- /dev/null +++ b/pkg/types/authtypes/typeable_metaresources.go @@ -0,0 +1,48 @@ +package authtypes + +import ( + "github.com/SigNoz/signoz/pkg/valuer" + openfgav1 "github.com/openfga/api/proto/openfga/v1" +) + +var _ Typeable = new(typeableMetaResources) + +type typeableMetaResources struct { + name Name +} + +func NewTypeableMetaResources(name Name) (Typeable, error) { + return &typeableMetaResources{name: name}, nil +} + +func MustNewTypeableMetaResources(name Name) Typeable { + resources, err := NewTypeableMetaResources(name) + if err != nil { + panic(err) + } + + return resources +} + +func (typeableResources *typeableMetaResources) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) { + tuples := make([]*openfgav1.TupleKey, 0) + for _, selector := range selector { + object := typeableResources.Prefix(orgID) + "/" + selector.String() + tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object}) + } + + return tuples, nil +} + +func (typeableMetaResources *typeableMetaResources) Type() Type { + return TypeMetaResources +} + +func (typeableMetaResources *typeableMetaResources) Name() Name { + return typeableMetaResources.name +} + +// example: metaresources:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboards +func (typeableMetaResources *typeableMetaResources) Prefix(orgID valuer.UUID) string { + return typeableMetaResources.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResources.Name().String() +} diff --git a/pkg/types/authtypes/typeable_resource.go b/pkg/types/authtypes/typeable_resource.go deleted file mode 100644 index e2bf5c5c7e..0000000000 --- a/pkg/types/authtypes/typeable_resource.go +++ /dev/null @@ -1,47 +0,0 @@ -package authtypes - -import ( - "github.com/SigNoz/signoz/pkg/valuer" - openfgav1 "github.com/openfga/api/proto/openfga/v1" -) - -var _ Typeable = new(typeableResource) - -type typeableResource struct { - name Name -} - -func NewTypeableResource(name Name) (Typeable, error) { - return &typeableResource{name: name}, nil -} - -func MustNewTypeableResource(name Name) Typeable { - typeableesource, err := NewTypeableResource(name) - if err != nil { - panic(err) - } - return typeableesource -} - -func (typeableResource *typeableResource) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) { - tuples := make([]*openfgav1.TupleKey, 0) - for _, selector := range selector { - object := typeableResource.Prefix(orgID) + "/" + selector.String() - tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object}) - } - - return tuples, nil -} - -func (typeableResource *typeableResource) Type() Type { - return TypeResource -} - -func (typeableResource *typeableResource) Name() Name { - return typeableResource.name -} - -func (typeableResource *typeableResource) Prefix(orgID valuer.UUID) string { - // example: resource:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboard - return typeableResource.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableResource.Name().String() -} diff --git a/pkg/types/authtypes/typeable_resources.go b/pkg/types/authtypes/typeable_resources.go deleted file mode 100644 index e2459e04b1..0000000000 --- a/pkg/types/authtypes/typeable_resources.go +++ /dev/null @@ -1,47 +0,0 @@ -package authtypes - -import ( - "github.com/SigNoz/signoz/pkg/valuer" - openfgav1 "github.com/openfga/api/proto/openfga/v1" -) - -var _ Typeable = new(typeableResources) - -type typeableResources struct { - name Name -} - -func NewTypeableResources(name Name) (Typeable, error) { - return &typeableResources{name: name}, nil -} - -func MustNewTypeableResources(name Name) Typeable { - resources, err := NewTypeableResources(name) - if err != nil { - panic(err) - } - return resources -} - -func (typeableResources *typeableResources) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) { - tuples := make([]*openfgav1.TupleKey, 0) - for _, selector := range selector { - object := typeableResources.Prefix(orgID) + "/" + selector.String() - tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object}) - } - - return tuples, nil -} - -func (typeableResources *typeableResources) Type() Type { - return TypeResources -} - -func (typeableResources *typeableResources) Name() Name { - return typeableResources.name -} - -func (typeableResources *typeableResources) Prefix(orgID valuer.UUID) string { - // example: resources:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboards - return typeableResources.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableResources.Name().String() -} diff --git a/pkg/types/authtypes/typeable_role.go b/pkg/types/authtypes/typeable_role.go index 47e7a924ed..5d855b89a6 100644 --- a/pkg/types/authtypes/typeable_role.go +++ b/pkg/types/authtypes/typeable_role.go @@ -27,7 +27,7 @@ func (typeableRole *typeableRole) Name() Name { return MustNewName("role") } +// example: role:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/role func (typeableRole *typeableRole) Prefix(orgID valuer.UUID) string { - // example: role:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/role return typeableRole.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableRole.Name().String() } diff --git a/pkg/types/authtypes/typeable_user.go b/pkg/types/authtypes/typeable_user.go index 6c09ece7e1..ad8b7ed3fe 100644 --- a/pkg/types/authtypes/typeable_user.go +++ b/pkg/types/authtypes/typeable_user.go @@ -27,7 +27,7 @@ func (typeableUser *typeableUser) Name() Name { return MustNewName("user") } +// example: user:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/user func (typeableUser *typeableUser) Prefix(orgID valuer.UUID) string { - // example: user:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/user return typeableUser.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableUser.Name().String() } diff --git a/pkg/types/dashboardtypes/dashboard.go b/pkg/types/dashboardtypes/dashboard.go index b076b24c66..2ccea1d65e 100644 --- a/pkg/types/dashboardtypes/dashboard.go +++ b/pkg/types/dashboardtypes/dashboard.go @@ -13,8 +13,8 @@ import ( ) var ( - TypeableResourceDashboard = authtypes.MustNewTypeableResource(authtypes.MustNewName("dashboard")) - TypeableResourcesDashboards = authtypes.MustNewTypeableResources(authtypes.MustNewName("dashboards")) + TypeableResourceDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("dashboard")) + TypeableResourcesDashboards = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("dashboards")) ) type StorableDashboard struct { diff --git a/pkg/types/roletypes/role.go b/pkg/types/roletypes/role.go index 5643148ead..b8905fbf83 100644 --- a/pkg/types/roletypes/role.go +++ b/pkg/types/roletypes/role.go @@ -22,7 +22,7 @@ var ( ) var ( - TypeableResourcesRoles = authtypes.MustNewTypeableResources(authtypes.MustNewName("roles")) + TypeableResourcesRoles = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("roles")) ) type StorableRole struct { diff --git a/pkg/valuer/email.go b/pkg/valuer/email.go index 020a76e5ee..aaae964608 100644 --- a/pkg/valuer/email.go +++ b/pkg/valuer/email.go @@ -103,3 +103,7 @@ func (enum *Email) UnmarshalText(text []byte) error { return nil } + +func (enum Email) MarshalText() (text []byte, err error) { + return []byte(enum.StringValue()), nil +} diff --git a/pkg/valuer/string.go b/pkg/valuer/string.go index 687cb19cab..2ec265971c 100644 --- a/pkg/valuer/string.go +++ b/pkg/valuer/string.go @@ -73,3 +73,7 @@ func (enum *String) UnmarshalText(text []byte) error { *enum = NewString(string(text)) return nil } + +func (enum String) MarshalText() (text []byte, err error) { + return []byte(enum.StringValue()), nil +} diff --git a/pkg/valuer/uuid.go b/pkg/valuer/uuid.go index da38eda9b5..d437c7ab52 100644 --- a/pkg/valuer/uuid.go +++ b/pkg/valuer/uuid.go @@ -136,3 +136,7 @@ func (enum *UUID) UnmarshalText(text []byte) error { *enum = uuid return nil } + +func (enum UUID) MarshalText() (text []byte, err error) { + return []byte(enum.StringValue()), nil +} diff --git a/pkg/valuer/valuer.go b/pkg/valuer/valuer.go index 846c9c9f79..715ff5bdca 100644 --- a/pkg/valuer/valuer.go +++ b/pkg/valuer/valuer.go @@ -39,4 +39,7 @@ type Valuer interface { // Implement encoding.TextUnmarshaler to allow the value to be unmarshalled from a string encoding.TextUnmarshaler + + // Implement encoding.TextUnmarshaler to allow the value to be marshalled unto a string + encoding.TextMarshaler }