feat(authz): scafolding for rbac migration (#10121)

* feat(authz): initial commit for migrating rbac to openfga

* feat(authz): make the role updates idempotant

* feat(authz): split role module into role and grant

* feat(authz): some naming changes

* feat(authz): integrate the grant module

* feat(authz): add support for migrating existing user role

* feat(authz): add support for migrating existing user role

* feat(authz): figure out the * selector

* feat(authz): merge main

* feat(authz): merge main

* feat(authz): address couple of todos

* feat(authz): address couple of todos

* feat(authz): fix tests and revert public dashboard change

* feat(authz): fix tests and revert public dashboard change

* feat(authz): add open api spec

* feat(authz): add open api spec

* feat(authz): add api key changes and missing migration

* feat(authz): split role into getter and setter

* feat(authz): add integration tests for authz register

* feat(authz): add more tests for user invite and delete

* feat(authz): update user tests

* feat(authz): rename grant to granter

* feat(authz): address review comments

* feat(authz): address review comments

* feat(authz): address review comments

* feat(authz): add the migration for existing roles

* feat(authz): go mod tidy

* feat(authz): fix integration tests

* feat(authz): handle community changes

* feat(authz): handle community changes

* feat(authz): role selectors for open claims

* feat(authz): role selectors for open claims

* feat(authz): prevent duplicate entries for changelog

* feat(authz): scafolding for rbac migration

* feat(authz): scafolding for rbac migration

* feat(authz): scafolding for rbac migration

* feat(authz): scafolding for rbac migration

* feat(authz): scafolding for rbac migration
This commit is contained in:
Vikrant Gupta
2026-01-27 21:24:36 +05:30
committed by GitHub
parent 9e9879fc8d
commit 1c815b130c
35 changed files with 1038 additions and 286 deletions

View File

@@ -19,6 +19,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/role" "github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
@@ -80,12 +81,15 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] { func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx)) return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
}, },
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, _ role.Module, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module { func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, _ role.Setter, _ role.Granter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser) return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
}, },
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] { func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return noopgateway.NewProviderFactory() return noopgateway.NewProviderFactory()
}, },
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, _ []role.RegisterTypeable) role.Setter {
return implrole.NewSetter(implrole.NewStore(store), authz)
},
) )
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", "error", err) logger.ErrorContext(ctx, "failed to create signoz", "error", err)

View File

@@ -14,6 +14,7 @@ import (
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing" enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing" "github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/ee/modules/role/implrole"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app" enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema" "github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore" "github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
@@ -29,6 +30,7 @@ import (
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/role" "github.com/SigNoz/signoz/pkg/modules/role"
pkgimplrole "github.com/SigNoz/signoz/pkg/modules/role/implrole"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
@@ -119,13 +121,17 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] { func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx)) return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
}, },
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, role role.Module, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module { func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, role, queryParser, querier, licensing) return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, roleSetter, granter, queryParser, querier, licensing)
}, },
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] { func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return httpgateway.NewProviderFactory(licensing) return httpgateway.NewProviderFactory(licensing)
}, },
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
return implrole.NewSetter(pkgimplrole.NewStore(store), authz, licensing, registry)
},
) )
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", "error", err) logger.ErrorContext(ctx, "failed to create signoz", "error", err)
return err return err

View File

@@ -209,7 +209,7 @@ paths:
/api/v1/dashboards/{id}/public: /api/v1/dashboards/{id}/public:
delete: delete:
deprecated: false deprecated: false
description: This endpoints deletes the public sharing config and disables the description: This endpoint deletes the public sharing config and disables the
public sharing of a dashboard public sharing of a dashboard
operationId: DeletePublicDashboard operationId: DeletePublicDashboard
parameters: parameters:
@@ -253,7 +253,7 @@ paths:
- dashboard - dashboard
get: get:
deprecated: false deprecated: false
description: This endpoints returns public sharing config for a dashboard description: This endpoint returns public sharing config for a dashboard
operationId: GetPublicDashboard operationId: GetPublicDashboard
parameters: parameters:
- in: path - in: path
@@ -301,7 +301,7 @@ paths:
- dashboard - dashboard
post: post:
deprecated: false deprecated: false
description: This endpoints creates public sharing config and enables public description: This endpoint creates public sharing config and enables public
sharing of the dashboard sharing of the dashboard
operationId: CreatePublicDashboard operationId: CreatePublicDashboard
parameters: parameters:
@@ -355,7 +355,7 @@ paths:
- dashboard - dashboard
put: put:
deprecated: false deprecated: false
description: This endpoints updates the public sharing config for a dashboard description: This endpoint updates the public sharing config for a dashboard
operationId: UpdatePublicDashboard operationId: UpdatePublicDashboard
parameters: parameters:
- in: path - in: path
@@ -671,7 +671,7 @@ paths:
/api/v1/global/config: /api/v1/global/config:
get: get:
deprecated: false deprecated: false
description: This endpoints returns global config description: This endpoint returns global config
operationId: GetGlobalConfig operationId: GetGlobalConfig
responses: responses:
"200": "200":
@@ -1447,8 +1447,7 @@ paths:
/api/v1/public/dashboards/{id}: /api/v1/public/dashboards/{id}:
get: get:
deprecated: false deprecated: false
description: This endpoints returns the sanitized dashboard data for public description: This endpoint returns the sanitized dashboard data for public access
access
operationId: GetPublicDashboardData operationId: GetPublicDashboardData
parameters: parameters:
- in: path - in: path
@@ -1579,6 +1578,228 @@ paths:
summary: Reset password summary: Reset password
tags: tags:
- users - users
/api/v1/roles:
get:
deprecated: false
description: This endpoint lists all roles
operationId: ListRoles
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/RoletypesRole'
type: array
status:
type: string
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List roles
tags:
- role
post:
deprecated: false
description: This endpoint creates a role
operationId: CreateRole
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesIdentifiable'
status:
type: string
type: object
description: Created
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create role
tags:
- role
/api/v1/roles/{id}:
delete:
deprecated: false
description: This endpoint deletes a role
operationId: DeleteRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete role
tags:
- role
get:
deprecated: false
description: This endpoint gets a role
operationId: GetRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/RoletypesRole'
status:
type: string
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Get role
tags:
- role
patch:
deprecated: false
description: This endpoint patches a role
operationId: PatchRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Patch role
tags:
- role
/api/v1/user: /api/v1/user:
get: get:
deprecated: false deprecated: false
@@ -3888,6 +4109,25 @@ components:
status: status:
type: string type: string
type: object type: object
RoletypesRole:
properties:
createdAt:
format: date-time
type: string
description:
type: string
id:
type: string
name:
type: string
orgId:
type: string
type:
type: string
updatedAt:
format: date-time
type: string
type: object
TypesChangePasswordRequest: TypesChangePasswordRequest:
properties: properties:
newPassword: newPassword:

View File

@@ -47,7 +47,7 @@ func (provider *provider) Check(ctx context.Context, tuple *openfgav1.TupleKey)
return provider.pkgAuthzService.Check(ctx, tuple) return provider.pkgAuthzService.Check(ctx, tuple)
} }
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error { func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil) subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil { if err != nil {
return err return err
@@ -66,7 +66,7 @@ func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims aut
return nil return nil
} }
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error { func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil) subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
if err != nil { if err != nil {
return err return err

View File

@@ -26,12 +26,13 @@ type module struct {
pkgDashboardModule dashboard.Module pkgDashboardModule dashboard.Module
store dashboardtypes.Store store dashboardtypes.Store
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
role role.Module roleSetter role.Setter
granter role.Granter
querier querier.Querier querier querier.Querier
licensing licensing.Licensing licensing licensing.Licensing
} }
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, role role.Module, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module { func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard") scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser) pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
@@ -39,7 +40,8 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
pkgDashboardModule: pkgDashboardModule, pkgDashboardModule: pkgDashboardModule,
store: store, store: store,
settings: scopedProviderSettings, settings: scopedProviderSettings,
role: role, roleSetter: roleSetter,
granter: granter,
querier: querier, querier: querier,
licensing: licensing, licensing: licensing,
} }
@@ -59,12 +61,12 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
return errors.Newf(errors.TypeAlreadyExists, dashboardtypes.ErrCodePublicDashboardAlreadyExists, "dashboard with id %s is already public", storablePublicDashboard.DashboardID) return errors.Newf(errors.TypeAlreadyExists, dashboardtypes.ErrCodePublicDashboardAlreadyExists, "dashboard with id %s is already public", storablePublicDashboard.DashboardID)
} }
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID)) role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
if err != nil { if err != nil {
return err return err
} }
err = module.role.Assign(ctx, role.ID, orgID, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil)) err = module.granter.Grant(ctx, orgID, roletypes.SigNozAnonymousRoleName, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil))
if err != nil { if err != nil {
return err return err
} }
@@ -77,7 +79,7 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()), authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
) )
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil) err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil)
if err != nil { if err != nil {
return err return err
} }
@@ -193,7 +195,7 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
return err return err
} }
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID)) role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
if err != nil { if err != nil {
return err return err
} }
@@ -206,7 +208,7 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()), authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
) )
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject}) err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
if err != nil { if err != nil {
return err return err
} }
@@ -270,7 +272,7 @@ func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashb
return err return err
} }
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID)) role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
if err != nil { if err != nil {
return err return err
} }
@@ -283,7 +285,7 @@ func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashb
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()), authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
) )
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject}) err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
if err != nil { if err != nil {
return err return err
} }

View File

@@ -0,0 +1,165 @@
package implrole
import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type setter struct {
store roletypes.Store
authz authz.AuthZ
licensing licensing.Licensing
registry []role.RegisterTypeable
}
func NewSetter(store roletypes.Store, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
return &setter{
store: store,
authz: authz,
licensing: licensing,
registry: registry,
}
}
func (setter *setter) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
_, err := setter.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
}
func (setter *setter) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
_, err := setter.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
existingRole, err := setter.store.GetByOrgIDAndName(ctx, role.OrgID, role.Name)
if err != nil {
if !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
}
if existingRole != nil {
return roletypes.NewRoleFromStorableRole(existingRole), nil
}
err = setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
if err != nil {
return nil, err
}
return role, nil
}
func (setter *setter) GetResources(_ context.Context) []*authtypes.Resource {
typeables := make([]authtypes.Typeable, 0)
for _, register := range setter.registry {
typeables = append(typeables, register.MustGetTypeables()...)
}
// role module cannot self register itself!
typeables = append(typeables, setter.MustGetTypeables()...)
resources := make([]*authtypes.Resource, 0)
for _, typeable := range typeables {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
return resources
}
func (setter *setter) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
storableRole, err := setter.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
objects := make([]*authtypes.Object, 0)
for _, resource := range setter.GetResources(ctx) {
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
resourceObjects, err := setter.
authz.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
relation,
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
}
}
return objects, nil
}
func (setter *setter) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
_, err := setter.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return setter.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
}
func (setter *setter) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
_, err := setter.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
additionTuples, err := roletypes.GetAdditionTuples(id, orgID, relation, additions)
if err != nil {
return err
}
deletionTuples, err := roletypes.GetDeletionTuples(id, orgID, relation, deletions)
if err != nil {
return err
}
err = setter.authz.Write(ctx, additionTuples, deletionTuples)
if err != nil {
return err
}
return nil
}
func (setter *setter) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
_, err := setter.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableRole, err := setter.store.Get(ctx, orgID, id)
if err != nil {
return err
}
role := roletypes.NewRoleFromStorableRole(storableRole)
err = role.CanEditDelete()
if err != nil {
return err
}
return setter.store.Delete(ctx, orgID, id)
}
func (setter *setter) MustGetTypeables() []authtypes.Typeable {
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
}

View File

@@ -211,7 +211,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) { func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
r := baseapp.NewRouter() r := baseapp.NewRouter()
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz) am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz, s.signoz.Modules.RoleGetter)
r.Use(otelmux.Middleware( r.Use(otelmux.Middleware(
"apiserver", "apiserver",

View File

@@ -17,7 +17,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
ID: "CreatePublicDashboard", ID: "CreatePublicDashboard",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Create public dashboard", Summary: "Create public dashboard",
Description: "This endpoints creates public sharing config and enables public sharing of the dashboard", Description: "This endpoint creates public sharing config and enables public sharing of the dashboard",
Request: new(dashboardtypes.PostablePublicDashboard), Request: new(dashboardtypes.PostablePublicDashboard),
RequestContentType: "", RequestContentType: "",
Response: new(types.Identifiable), Response: new(types.Identifiable),
@@ -34,7 +34,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
ID: "GetPublicDashboard", ID: "GetPublicDashboard",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Get public dashboard", Summary: "Get public dashboard",
Description: "This endpoints returns public sharing config for a dashboard", Description: "This endpoint returns public sharing config for a dashboard",
Request: nil, Request: nil,
RequestContentType: "", RequestContentType: "",
Response: new(dashboardtypes.GettablePublicDasbhboard), Response: new(dashboardtypes.GettablePublicDasbhboard),
@@ -51,7 +51,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
ID: "UpdatePublicDashboard", ID: "UpdatePublicDashboard",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Update public dashboard", Summary: "Update public dashboard",
Description: "This endpoints updates the public sharing config for a dashboard", Description: "This endpoint updates the public sharing config for a dashboard",
Request: new(dashboardtypes.UpdatablePublicDashboard), Request: new(dashboardtypes.UpdatablePublicDashboard),
RequestContentType: "", RequestContentType: "",
Response: nil, Response: nil,
@@ -68,7 +68,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
ID: "DeletePublicDashboard", ID: "DeletePublicDashboard",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Delete public dashboard", Summary: "Delete public dashboard",
Description: "This endpoints deletes the public sharing config and disables the public sharing of a dashboard", Description: "This endpoint deletes the public sharing config and disables the public sharing of a dashboard",
Request: nil, Request: nil,
RequestContentType: "", RequestContentType: "",
Response: nil, Response: nil,
@@ -83,7 +83,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authZ.CheckWithoutClaims( if err := router.Handle("/api/v1/public/dashboards/{id}", handler.New(provider.authZ.CheckWithoutClaims(
provider.dashboardHandler.GetPublicData, provider.dashboardHandler.GetPublicData,
authtypes.RelationRead, authtypes.RelationRead, authtypes.RelationRead,
dashboardtypes.TypeableMetaResourcePublicDashboard, dashboardtypes.TypeableMetaResourcePublicDashboard,
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) { func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
id, err := valuer.NewUUID(mux.Vars(req)["id"]) id, err := valuer.NewUUID(mux.Vars(req)["id"])
@@ -92,11 +92,11 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
} }
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs) return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
}), handler.OpenAPIDef{ }, []string{}), handler.OpenAPIDef{
ID: "GetPublicDashboardData", ID: "GetPublicDashboardData",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Get public dashboard data", Summary: "Get public dashboard data",
Description: "This endpoints returns the sanitized dashboard data for public access", Description: "This endpoint returns the sanitized dashboard data for public access",
Request: nil, Request: nil,
RequestContentType: "", RequestContentType: "",
Response: new(dashboardtypes.GettablePublicDashboardData), Response: new(dashboardtypes.GettablePublicDashboardData),
@@ -111,7 +111,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authZ.CheckWithoutClaims( if err := router.Handle("/api/v1/public/dashboards/{id}/widgets/{idx}/query_range", handler.New(provider.authZ.CheckWithoutClaims(
provider.dashboardHandler.GetPublicWidgetQueryRange, provider.dashboardHandler.GetPublicWidgetQueryRange,
authtypes.RelationRead, authtypes.RelationRead, authtypes.RelationRead,
dashboardtypes.TypeableMetaResourcePublicDashboard, dashboardtypes.TypeableMetaResourcePublicDashboard,
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) { func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
id, err := valuer.NewUUID(mux.Vars(req)["id"]) id, err := valuer.NewUUID(mux.Vars(req)["id"])
@@ -120,7 +120,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
} }
return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs) return provider.dashboardModule.GetPublicDashboardSelectorsAndOrg(req.Context(), id, orgs)
}), handler.OpenAPIDef{ }, []string{}), handler.OpenAPIDef{
ID: "GetPublicDashboardWidgetQueryRange", ID: "GetPublicDashboardWidgetQueryRange",
Tags: []string{"dashboard"}, Tags: []string{"dashboard"},
Summary: "Get query range result", Summary: "Get query range result",

View File

@@ -13,7 +13,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
ID: "GetGlobalConfig", ID: "GetGlobalConfig",
Tags: []string{"global"}, Tags: []string{"global"},
Summary: "Get global config", Summary: "Get global config",
Description: "This endpoints returns global config", Description: "This endpoint returns global config",
Request: nil, Request: nil,
RequestContentType: "", RequestContentType: "",
Response: new(types.GettableGlobalConfig), Response: new(types.GettableGlobalConfig),

View File

@@ -17,6 +17,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote" "github.com/SigNoz/signoz/pkg/modules/promote"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/session" "github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user" "github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
@@ -41,6 +42,8 @@ type provider struct {
dashboardHandler dashboard.Handler dashboardHandler dashboard.Handler
metricsExplorerHandler metricsexplorer.Handler metricsExplorerHandler metricsexplorer.Handler
gatewayHandler gateway.Handler gatewayHandler gateway.Handler
roleGetter role.Getter
roleHandler role.Handler
} }
func NewFactory( func NewFactory(
@@ -58,9 +61,11 @@ func NewFactory(
dashboardHandler dashboard.Handler, dashboardHandler dashboard.Handler,
metricsExplorerHandler metricsexplorer.Handler, metricsExplorerHandler metricsexplorer.Handler,
gatewayHandler gateway.Handler, gatewayHandler gateway.Handler,
roleGetter role.Getter,
roleHandler role.Handler,
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] { ) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) { return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, providerSettings factory.ProviderSettings, config apiserver.Config) (apiserver.APIServer, error) {
return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler, flaggerHandler, dashboardModule, dashboardHandler, metricsExplorerHandler, gatewayHandler) return newProvider(ctx, providerSettings, config, orgGetter, authz, orgHandler, userHandler, sessionHandler, authDomainHandler, preferenceHandler, globalHandler, promoteHandler, flaggerHandler, dashboardModule, dashboardHandler, metricsExplorerHandler, gatewayHandler, roleGetter, roleHandler)
}) })
} }
@@ -82,6 +87,8 @@ func newProvider(
dashboardHandler dashboard.Handler, dashboardHandler dashboard.Handler,
metricsExplorerHandler metricsexplorer.Handler, metricsExplorerHandler metricsexplorer.Handler,
gatewayHandler gateway.Handler, gatewayHandler gateway.Handler,
roleGetter role.Getter,
roleHandler role.Handler,
) (apiserver.APIServer, error) { ) (apiserver.APIServer, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver") settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/apiserver/signozapiserver")
router := mux.NewRouter().UseEncodedPath() router := mux.NewRouter().UseEncodedPath()
@@ -102,9 +109,11 @@ func newProvider(
dashboardHandler: dashboardHandler, dashboardHandler: dashboardHandler,
metricsExplorerHandler: metricsExplorerHandler, metricsExplorerHandler: metricsExplorerHandler,
gatewayHandler: gatewayHandler, gatewayHandler: gatewayHandler,
roleGetter: roleGetter,
roleHandler: roleHandler,
} }
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz) provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz, roleGetter)
if err := provider.AddToRouter(router); err != nil { if err := provider.AddToRouter(router); err != nil {
return nil, err return nil, err
@@ -162,6 +171,10 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err return err
} }
if err := provider.addRoleRoutes(router); err != nil {
return err
}
return nil return nil
} }

View File

@@ -0,0 +1,99 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/gorilla/mux"
)
func (provider *provider) addRoleRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Create), handler.OpenAPIDef{
ID: "CreateRole",
Tags: []string{"role"},
Summary: "Create role",
Description: "This endpoint creates a role",
Request: nil,
RequestContentType: "",
Response: new(types.Identifiable),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/roles", handler.New(provider.authZ.AdminAccess(provider.roleHandler.List), handler.OpenAPIDef{
ID: "ListRoles",
Tags: []string{"role"},
Summary: "List roles",
Description: "This endpoint lists all roles",
Request: nil,
RequestContentType: "",
Response: make([]*roletypes.Role, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Get), handler.OpenAPIDef{
ID: "GetRole",
Tags: []string{"role"},
Summary: "Get role",
Description: "This endpoint gets a role",
Request: nil,
RequestContentType: "",
Response: new(roletypes.Role),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Patch), handler.OpenAPIDef{
ID: "PatchRole",
Tags: []string{"role"},
Summary: "Patch role",
Description: "This endpoint patches a role",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/roles/{id}", handler.New(provider.authZ.AdminAccess(provider.roleHandler.Delete), handler.OpenAPIDef{
ID: "DeleteRole",
Tags: []string{"role"},
Summary: "Delete role",
Description: "This endpoint deletes a role",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -16,9 +16,10 @@ type AuthZ interface {
Check(context.Context, *openfgav1.TupleKey) error Check(context.Context, *openfgav1.TupleKey) error
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does. // CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error // CheckWithTupleCreationWithoutClaims checks permissions for anonymous users.
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Typeable, []authtypes.Selector, []authtypes.Selector) error
// Batch Check returns error when the upstream authorization server is unavailable or for all the tuples of subject (s) doesn't have relation (r) on object (o). // Batch Check returns error when the upstream authorization server is unavailable or for all the tuples of subject (s) doesn't have relation (r) on object (o).
BatchCheck(context.Context, []*openfgav1.TupleKey) error BatchCheck(context.Context, []*openfgav1.TupleKey) error

View File

@@ -152,17 +152,17 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.
} }
} }
return errors.New(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "") return errors.Newf(errors.TypeForbidden, authtypes.ErrCodeAuthZForbidden, "none of the subjects are allowed for requested access")
} }
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error { func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil) subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil { if err != nil {
return err return err
} }
tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID) tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
if err != nil { if err != nil {
return err return err
} }
@@ -175,13 +175,13 @@ func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims aut
return nil return nil
} }
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error { func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil) subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
if err != nil { if err != nil {
return err return err
} }
tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID) tuples, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)
if err != nil { if err != nil {
return err return err
} }
@@ -195,6 +195,10 @@ func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Contex
} }
func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error { func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
if len(additions) == 0 && len(deletions) == 0 {
return nil
}
storeID, modelID := provider.getStoreIDandModelID() storeID, modelID := provider.getStoreIDandModelID()
deletionTuplesWithoutCondition := make([]*openfgav1.TupleKeyWithoutCondition, len(deletions)) deletionTuplesWithoutCondition := make([]*openfgav1.TupleKeyWithoutCondition, len(deletions))
for idx, tuple := range deletions { for idx, tuple := range deletions {

View File

@@ -34,11 +34,11 @@ func TestProviderStartStop(t *testing.T) {
sqlstore.Mock().ExpectQuery("SELECT authorization_model_id, schema_version, type, type_definition, serialized_protobuf FROM authorization_model WHERE authorization_model_id = (.+) AND store = (.+)").WithArgs("01K44QQKXR6F729W160NFCJT58", "01K3V0NTN47MPTMEV1PD5ST6ZC").WillReturnRows(modelRows) sqlstore.Mock().ExpectQuery("SELECT authorization_model_id, schema_version, type, type_definition, serialized_protobuf FROM authorization_model WHERE authorization_model_id = (.+) AND store = (.+)").WithArgs("01K44QQKXR6F729W160NFCJT58", "01K3V0NTN47MPTMEV1PD5ST6ZC").WillReturnRows(modelRows)
sqlstore.Mock().ExpectExec("INSERT INTO authorization_model (.+) VALUES (.+)").WillReturnResult(sqlmock.NewResult(1, 1)) sqlstore.Mock().ExpectExec("INSERT INTO authorization_model (.+) VALUES (.+)").WillReturnResult(sqlmock.NewResult(1, 1))
go func() { go func() {
err := provider.Start(context.Background()) err := provider.Start(context.Background())
require.NoError(t, err) require.NoError(t, err)
}() }()
// wait for the service to start // wait for the service to start
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)

View File

@@ -7,6 +7,7 @@ import (
"github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/http/render" "github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -20,14 +21,15 @@ type AuthZ struct {
logger *slog.Logger logger *slog.Logger
orgGetter organization.Getter orgGetter organization.Getter
authzService authz.AuthZ authzService authz.AuthZ
roleGetter role.Getter
} }
func NewAuthZ(logger *slog.Logger, orgGetter organization.Getter, authzService authz.AuthZ) *AuthZ { func NewAuthZ(logger *slog.Logger, orgGetter organization.Getter, authzService authz.AuthZ, roleGetter role.Getter) *AuthZ {
if logger == nil { if logger == nil {
panic("cannot build authz middleware, logger is empty") panic("cannot build authz middleware, logger is empty")
} }
return &AuthZ{logger: logger, orgGetter: orgGetter, authzService: authzService} return &AuthZ{logger: logger, orgGetter: orgGetter, authzService: authzService, roleGetter: roleGetter}
} }
func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc { func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
@@ -109,9 +111,10 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
}) })
} }
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn) http.HandlerFunc { func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn, roles []string) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context()) ctx := req.Context()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -129,7 +132,18 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
return return
} }
err = middleware.authzService.CheckWithTupleCreation(req.Context(), claims, orgId, relation, translation, typeable, selectors) roles, err := middleware.roleGetter.ListByOrgIDAndNames(req.Context(), orgId, roles)
if err != nil {
render.Error(rw, err)
return
}
roleSelectors := []authtypes.Selector{}
for _, role := range roles {
selectors = append(selectors, authtypes.MustNewSelector(authtypes.TypeRole, role.ID.String()))
}
err = middleware.authzService.CheckWithTupleCreation(ctx, claims, orgId, relation, typeable, selectors, roleSelectors)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -139,7 +153,7 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
}) })
} }
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn) http.HandlerFunc { func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn, roles []string) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context() ctx := req.Context()
orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx) orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx)
@@ -154,7 +168,7 @@ func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation auth
return return
} }
err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, translation, typeable, selectors) err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, selectors)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return

View File

@@ -0,0 +1,63 @@
package implrole
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type getter struct {
store roletypes.Store
}
func NewGetter(store roletypes.Store) role.Getter {
return &getter{store: store}
}
func (getter *getter) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
storableRole, err := getter.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
return roletypes.NewRoleFromStorableRole(storableRole), nil
}
func (getter *getter) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
storableRole, err := getter.store.GetByOrgIDAndName(ctx, orgID, name)
if err != nil {
return nil, err
}
return roletypes.NewRoleFromStorableRole(storableRole), nil
}
func (getter *getter) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
storableRoles, err := getter.store.List(ctx, orgID)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
for idx, storableRole := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
}
return roles, nil
}
func (getter *getter) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
storableRoles, err := getter.store.ListByOrgIDAndNames(ctx, orgID, names)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
for idx, storable := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
}
return roles, nil
}

View File

@@ -0,0 +1,108 @@
package implrole
import (
"context"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type granter struct {
store roletypes.Store
authz authz.AuthZ
}
func NewGranter(store roletypes.Store, authz authz.AuthZ) role.Granter {
return &granter{store: store, authz: authz}
}
func (granter *granter) Grant(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
role, err := granter.store.GetByOrgIDAndName(ctx, orgID, name)
if err != nil {
return err
}
tuples, err := authtypes.TypeableRole.Tuples(
subject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, role.ID.StringValue()),
},
orgID,
)
if err != nil {
return err
}
return granter.authz.Write(ctx, tuples, nil)
}
func (granter *granter) GrantByID(ctx context.Context, orgID valuer.UUID, id valuer.UUID, subject string) error {
tuples, err := authtypes.TypeableRole.Tuples(
subject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, id.StringValue()),
},
orgID,
)
if err != nil {
return err
}
return granter.authz.Write(ctx, tuples, nil)
}
func (granter *granter) ModifyGrant(ctx context.Context, orgID valuer.UUID, existingRoleName string, updatedRoleName string, subject string) error {
err := granter.Revoke(ctx, orgID, existingRoleName, subject)
if err != nil {
return err
}
err = granter.Grant(ctx, orgID, updatedRoleName, subject)
if err != nil {
return err
}
return nil
}
func (granter *granter) Revoke(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
role, err := granter.store.GetByOrgIDAndName(ctx, orgID, name)
if err != nil {
return err
}
tuples, err := authtypes.TypeableRole.Tuples(
subject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, role.ID.StringValue()),
},
orgID,
)
if err != nil {
return err
}
return granter.authz.Write(ctx, nil, tuples)
}
func (granter *granter) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*roletypes.Role) error {
err := granter.store.RunInTx(ctx, func(ctx context.Context) error {
for _, role := range managedRoles {
err := granter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return nil
}

View File

@@ -14,11 +14,12 @@ import (
) )
type handler struct { type handler struct {
module role.Module setter role.Setter
getter role.Getter
} }
func NewHandler(module role.Module) role.Handler { func NewHandler(setter role.Setter, getter role.Getter) role.Handler {
return &handler{module: module} return &handler{setter: setter, getter: getter}
} }
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) { func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
@@ -35,7 +36,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
return return
} }
err = handler.module.Create(ctx, roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom.StringValue(), valuer.MustNewUUID(claims.OrgID))) err = handler.setter.Create(ctx, valuer.MustNewUUID(claims.OrgID), roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID)))
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -63,7 +64,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
return return
} }
role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), roleID) role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), roleID)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -102,7 +103,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
return return
} }
objects, err := handler.module.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation) objects, err := handler.setter.GetObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -113,7 +114,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) { func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
resources := handler.module.GetResources(ctx) resources := handler.setter.GetResources(ctx)
var resourceRelations = struct { var resourceRelations = struct {
Resources []*authtypes.Resource `json:"resources"` Resources []*authtypes.Resource `json:"resources"`
@@ -133,7 +134,7 @@ func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
return return
} }
roles, err := handler.module.List(ctx, valuer.MustNewUUID(claims.OrgID)) roles, err := handler.getter.List(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -162,14 +163,19 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
return return
} }
role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id) role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
role.PatchMetadata(req.Name, req.Description) err = role.PatchMetadata(req.Name, req.Description)
err = handler.module.Patch(ctx, valuer.MustNewUUID(claims.OrgID), role) if err != nil {
render.Error(rw, err)
return
}
err = handler.setter.Patch(ctx, valuer.MustNewUUID(claims.OrgID), role)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -204,13 +210,19 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
return return
} }
patchableObjects, err := roletypes.NewPatchableObjects(req.Additions, req.Deletions, relation) role, err := handler.getter.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
} }
err = handler.module.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), id, relation, patchableObjects.Additions, patchableObjects.Deletions) patchableObjects, err := role.NewPatchableObjects(req.Additions, req.Deletions, relation)
if err != nil {
render.Error(rw, err)
return
}
err = handler.setter.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), id, relation, patchableObjects.Additions, patchableObjects.Deletions)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return
@@ -233,7 +245,7 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
return return
} }
err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id) err = handler.setter.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return

View File

@@ -1,164 +0,0 @@
package implrole
import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store roletypes.Store
registry []role.RegisterTypeable
authz authz.AuthZ
}
func NewModule(store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) role.Module {
return &module{
store: store,
authz: authz,
registry: registry,
}
}
func (module *module) Create(ctx context.Context, role *roletypes.Role) error {
return module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
}
func (module *module) GetOrCreate(ctx context.Context, role *roletypes.Role) (*roletypes.Role, error) {
existingRole, err := module.store.GetByNameAndOrgID(ctx, role.Name, role.OrgID)
if err != nil {
if !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
}
if existingRole != nil {
return roletypes.NewRoleFromStorableRole(existingRole), nil
}
err = module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
if err != nil {
return nil, err
}
return role, nil
}
func (module *module) GetResources(_ context.Context) []*authtypes.Resource {
typeables := make([]authtypes.Typeable, 0)
for _, register := range module.registry {
typeables = append(typeables, register.MustGetTypeables()...)
}
// role module cannot self register itself!
typeables = append(typeables, module.MustGetTypeables()...)
resources := make([]*authtypes.Resource, 0)
for _, typeable := range typeables {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
return resources
}
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
storableRole, err := module.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
return roletypes.NewRoleFromStorableRole(storableRole), nil
}
func (module *module) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
storableRole, err := module.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
objects := make([]*authtypes.Object, 0)
for _, resource := range module.GetResources(ctx) {
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
resourceObjects, err := module.
authz.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
relation,
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
}
}
return objects, nil
}
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
storableRoles, err := module.store.List(ctx, orgID)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
for idx, storableRole := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
}
return roles, nil
}
func (module *module) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
return module.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
}
func (module *module) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
additionTuples, err := roletypes.GetAdditionTuples(id, orgID, relation, additions)
if err != nil {
return err
}
deletionTuples, err := roletypes.GetDeletionTuples(id, orgID, relation, deletions)
if err != nil {
return err
}
err = module.authz.Write(ctx, additionTuples, deletionTuples)
if err != nil {
return err
}
return nil
}
func (module *module) Assign(ctx context.Context, id valuer.UUID, orgID valuer.UUID, subject string) error {
tuples, err := authtypes.TypeableRole.Tuples(
subject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, id.StringValue()),
},
orgID,
)
if err != nil {
return err
}
return module.authz.Write(ctx, tuples, nil)
}
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
return module.store.Delete(ctx, orgID, id)
}
func (module *module) MustGetTypeables() []authtypes.Typeable {
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
}

View File

@@ -0,0 +1,53 @@
package implrole
import (
"context"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type setter struct {
store roletypes.Store
authz authz.AuthZ
}
func NewSetter(store roletypes.Store, authz authz.AuthZ) role.Setter {
return &setter{store: store, authz: authz}
}
func (setter *setter) Create(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) GetOrCreate(_ context.Context, _ valuer.UUID, _ *roletypes.Role) (*roletypes.Role, error) {
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) GetResources(_ context.Context) []*authtypes.Resource {
return nil
}
func (setter *setter) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) Patch(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) PatchObjects(_ context.Context, _ valuer.UUID, _ valuer.UUID, _ authtypes.Relation, _, _ []*authtypes.Object) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
}
func (setter *setter) MustGetTypeables() []authtypes.Typeable {
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/roletypes" "github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
) )
type store struct { type store struct {
@@ -20,7 +21,7 @@ func NewStore(sqlstore sqlstore.SQLStore) roletypes.Store {
func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error { func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error {
_, err := store. _, err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewInsert(). NewInsert().
Model(role). Model(role).
Exec(ctx) Exec(ctx)
@@ -35,7 +36,7 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
role := new(roletypes.StorableRole) role := new(roletypes.StorableRole)
err := store. err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewSelect(). NewSelect().
Model(role). Model(role).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
@@ -48,11 +49,11 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
return role, nil return role, nil
} }
func (store *store) GetByNameAndOrgID(ctx context.Context, name string, orgID valuer.UUID) (*roletypes.StorableRole, error) { func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.StorableRole, error) {
role := new(roletypes.StorableRole) role := new(roletypes.StorableRole)
err := store. err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewSelect(). NewSelect().
Model(role). Model(role).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
@@ -69,13 +70,30 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S
roles := make([]*roletypes.StorableRole, 0) roles := make([]*roletypes.StorableRole, 0)
err := store. err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewSelect(). NewSelect().
Model(&roles). Model(&roles).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
Scan(ctx) Scan(ctx)
if err != nil { if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "no roles found in org_id: %s", orgID) return nil, err
}
return roles, nil
}
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.StorableRole, error) {
roles := make([]*roletypes.StorableRole, 0)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(&roles).
Where("org_id = ?", orgID).
Where("name IN (?)", bun.In(names)).
Scan(ctx)
if err != nil {
return nil, err
} }
return roles, nil return roles, nil
@@ -84,7 +102,7 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error { func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error {
_, err := store. _, err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewUpdate(). NewUpdate().
Model(role). Model(role).
WherePK(). WherePK().
@@ -100,7 +118,7 @@ func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletyp
func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error { func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
_, err := store. _, err := store.
sqlstore. sqlstore.
BunDB(). BunDBCtx(ctx).
NewDelete(). NewDelete().
Model(new(roletypes.StorableRole)). Model(new(roletypes.StorableRole)).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).

View File

@@ -9,22 +9,16 @@ import (
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
type Module interface { type Setter interface {
// Creates the role. // Creates the role.
Create(context.Context, *roletypes.Role) error Create(context.Context, valuer.UUID, *roletypes.Role) error
// Gets the role if it exists or creates one. // Gets the role if it exists or creates one.
GetOrCreate(context.Context, *roletypes.Role) (*roletypes.Role, error) GetOrCreate(context.Context, valuer.UUID, *roletypes.Role) (*roletypes.Role, error)
// Gets the role
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
// Gets the objects associated with the given role and relation. // Gets the objects associated with the given role and relation.
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error) GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
// Lists all the roles for the organization.
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
// Gets all the typeable resources registered from role registry. // Gets all the typeable resources registered from role registry.
GetResources(context.Context) []*authtypes.Resource GetResources(context.Context) []*authtypes.Resource
@@ -37,12 +31,40 @@ type Module interface {
// Deletes the role and tuples in authorization server. // Deletes the role and tuples in authorization server.
Delete(context.Context, valuer.UUID, valuer.UUID) error Delete(context.Context, valuer.UUID, valuer.UUID) error
// Assigns role to the given subject.
Assign(context.Context, valuer.UUID, valuer.UUID, string) error
RegisterTypeable RegisterTypeable
} }
type Getter interface {
// Gets the role
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
// Gets the role by org_id and name
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*roletypes.Role, error)
// Lists all the roles for the organization.
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
// Lists all the roles for the organization filtered by name
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*roletypes.Role, error)
}
type Granter interface {
// Grants a role to the subject based on role name.
Grant(context.Context, valuer.UUID, string, string) error
// Grants a role to the subject based on role id.
GrantByID(context.Context, valuer.UUID, valuer.UUID, string) error
// Revokes a granted role from the subject based on role name.
Revoke(context.Context, valuer.UUID, string, string) error
// Changes the granted role for the subject based on role name.
ModifyGrant(context.Context, valuer.UUID, string, string, string) error
// Bootstrap the managed roles.
CreateManagedRoles(context.Context, valuer.UUID, []*roletypes.Role) error
}
type RegisterTypeable interface { type RegisterTypeable interface {
MustGetTypeables() []authtypes.Typeable MustGetTypeables() []authtypes.Typeable
} }

View File

@@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/user" "github.com/SigNoz/signoz/pkg/modules/user"
root "github.com/SigNoz/signoz/pkg/modules/user" root "github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/tokenizer" "github.com/SigNoz/signoz/pkg/tokenizer"
@@ -29,12 +30,13 @@ type Module struct {
emailing emailing.Emailing emailing emailing.Emailing
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
orgSetter organization.Setter orgSetter organization.Setter
granter role.Granter
analytics analytics.Analytics analytics analytics.Analytics
config user.Config config user.Config
} }
// This module is a WIP, don't take inspiration from this. // This module is a WIP, don't take inspiration from this.
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, analytics analytics.Analytics, config user.Config) root.Module { func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, granter role.Granter, analytics analytics.Analytics, config user.Config) root.Module {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser") settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
return &Module{ return &Module{
store: store, store: store,
@@ -43,6 +45,7 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
settings: settings, settings: settings,
orgSetter: orgSetter, orgSetter: orgSetter,
analytics: analytics, analytics: analytics,
granter: granter,
config: config, config: config,
} }
} }
@@ -227,7 +230,6 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
} }
user.UpdatedAt = time.Now() user.UpdatedAt = time.Now()
updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user) updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -258,8 +260,8 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
return updatedUser, nil return updatedUser, nil
} }
func (m *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error { func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
user, err := m.store.GetUser(ctx, valuer.MustNewUUID(id)) user, err := module.store.GetUser(ctx, valuer.MustNewUUID(id))
if err != nil { if err != nil {
return err return err
} }
@@ -269,7 +271,7 @@ func (m *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, d
} }
// don't allow to delete the last admin user // don't allow to delete the last admin user
adminUsers, err := m.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID) adminUsers, err := module.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
if err != nil { if err != nil {
return err return err
} }
@@ -278,11 +280,11 @@ func (m *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, d
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin") return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
} }
if err := m.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil { if err := module.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil {
return err return err
} }
m.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Deleted", map[string]any{ module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Deleted", map[string]any{
"deleted_by": deletedBy, "deleted_by": deletedBy,
}) })
@@ -476,7 +478,7 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
} }
if err = module.store.RunInTx(ctx, func(ctx context.Context) error { if err = module.store.RunInTx(ctx, func(ctx context.Context) error {
err := module.orgSetter.Create(ctx, organization) err = module.orgSetter.Create(ctx, organization)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -209,7 +209,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap) r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
r.Use(middleware.NewComment().Wrap) r.Use(middleware.NewComment().Wrap)
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz) am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz, s.signoz.Modules.RoleGetter)
api.RegisterRoutes(r, am) api.RegisterRoutes(r, am)
api.RegisterLogsRoutes(r, am) api.RegisterLogsRoutes(r, am)

View File

@@ -17,6 +17,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter" "github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport" "github.com/SigNoz/signoz/pkg/modules/rawdataexport"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport" "github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
"github.com/SigNoz/signoz/pkg/modules/savedview" "github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
"github.com/SigNoz/signoz/pkg/modules/services" "github.com/SigNoz/signoz/pkg/modules/services"
@@ -41,6 +43,7 @@ type Handlers struct {
Global global.Handler Global global.Handler
FlaggerHandler flagger.Handler FlaggerHandler flagger.Handler
GatewayHandler gateway.Handler GatewayHandler gateway.Handler
Role role.Handler
} }
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing, global global.Global, flaggerService flagger.Flagger, gatewayService gateway.Gateway) Handlers { func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing, global global.Global, flaggerService flagger.Flagger, gatewayService gateway.Gateway) Handlers {
@@ -57,5 +60,6 @@ func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, que
Global: signozglobal.NewHandler(global), Global: signozglobal.NewHandler(global),
FlaggerHandler: flagger.NewHandler(flaggerService), FlaggerHandler: flagger.NewHandler(flaggerService),
GatewayHandler: gateway.NewHandler(gatewayService), GatewayHandler: gateway.NewHandler(gatewayService),
Role: implrole.NewHandler(modules.RoleSetter, modules.RoleGetter),
} }
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory/factorytest" "github.com/SigNoz/signoz/pkg/factory/factorytest"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder" "github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@@ -40,7 +41,10 @@ func TestNewHandlers(t *testing.T) {
queryParser := queryparser.New(providerSettings) queryParser := queryparser.New(providerSettings)
require.NoError(t, err) require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser) dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule) roleSetter := implrole.NewSetter(implrole.NewStore(sqlstore), nil)
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
grantModule := implrole.NewGranter(implrole.NewStore(sqlstore), nil)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, roleSetter, roleGetter, grantModule)
handlers := NewHandlers(modules, providerSettings, nil, nil, nil, nil, nil) handlers := NewHandlers(modules, providerSettings, nil, nil, nil, nil, nil)

View File

@@ -25,6 +25,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter" "github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport" "github.com/SigNoz/signoz/pkg/modules/rawdataexport"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport" "github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/savedview" "github.com/SigNoz/signoz/pkg/modules/savedview"
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview" "github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
"github.com/SigNoz/signoz/pkg/modules/services" "github.com/SigNoz/signoz/pkg/modules/services"
@@ -66,6 +67,9 @@ type Modules struct {
SpanPercentile spanpercentile.Module SpanPercentile spanpercentile.Module
MetricsExplorer metricsexplorer.Module MetricsExplorer metricsexplorer.Module
Promote promote.Module Promote promote.Module
RoleSetter role.Setter
RoleGetter role.Getter
Granter role.Granter
} }
func NewModules( func NewModules(
@@ -85,10 +89,13 @@ func NewModules(
queryParser queryparser.QueryParser, queryParser queryparser.QueryParser,
config Config, config Config,
dashboard dashboard.Module, dashboard dashboard.Module,
roleSetter role.Setter,
roleGetter role.Getter,
granter role.Granter,
) Modules { ) Modules {
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore)) quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter) orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, analytics, config.User) user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, granter, analytics, config.User)
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings)) userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings) ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
@@ -110,5 +117,8 @@ func NewModules(
Services: implservices.NewModule(querier, telemetryStore), Services: implservices.NewModule(querier, telemetryStore),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer), MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore), Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
RoleSetter: roleSetter,
RoleGetter: roleGetter,
Granter: granter,
} }
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory/factorytest" "github.com/SigNoz/signoz/pkg/factory/factorytest"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
"github.com/SigNoz/signoz/pkg/queryparser" "github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder" "github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@@ -40,7 +41,10 @@ func TestNewModules(t *testing.T) {
queryParser := queryparser.New(providerSettings) queryParser := queryparser.New(providerSettings)
require.NoError(t, err) require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser) dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule) roleSetter := implrole.NewSetter(implrole.NewStore(sqlstore), nil)
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
grantModule := implrole.NewGranter(implrole.NewStore(sqlstore), nil)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, roleSetter, roleGetter, grantModule)
reflectVal := reflect.ValueOf(modules) reflectVal := reflect.ValueOf(modules)
for i := 0; i < reflectVal.NumField(); i++ { for i := 0; i < reflectVal.NumField(); i++ {

View File

@@ -19,6 +19,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/modules/promote" "github.com/SigNoz/signoz/pkg/modules/promote"
"github.com/SigNoz/signoz/pkg/modules/role"
"github.com/SigNoz/signoz/pkg/modules/session" "github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user" "github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types/ctxtypes" "github.com/SigNoz/signoz/pkg/types/ctxtypes"
@@ -49,6 +50,8 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ dashboard.Handler }{}, struct{ dashboard.Handler }{},
struct{ metricsexplorer.Handler }{}, struct{ metricsexplorer.Handler }{},
struct{ gateway.Handler }{}, struct{ gateway.Handler }{},
struct{ role.Getter }{},
struct{ role.Handler }{},
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{}) ).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -243,6 +243,8 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
handlers.Dashboard, handlers.Dashboard,
handlers.MetricsExplorer, handlers.MetricsExplorer,
handlers.GatewayHandler, handlers.GatewayHandler,
modules.RoleGetter,
handlers.Role,
), ),
) )
} }

View File

@@ -90,8 +90,9 @@ func New(
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]], telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error), authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
authzCallback func(context.Context, sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config], authzCallback func(context.Context, sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config],
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, role.Module, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module, dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, role.Setter, role.Granter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config], gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
roleSetterCallback func(sqlstore.SQLStore, authz.AuthZ, licensing.Licensing, []role.RegisterTypeable) role.Setter,
) (*SigNoz, error) { ) (*SigNoz, error) {
// Initialize instrumentation // Initialize instrumentation
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz") instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
@@ -280,6 +281,12 @@ func New(
return nil, err return nil, err
} }
// Initialize user getter
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
// Initialize the role getter
roleGetter := implrole.NewGetter(implrole.NewStore(sqlstore))
// Initialize authz // Initialize authz
authzProviderFactory := authzCallback(ctx, sqlstore) authzProviderFactory := authzCallback(ctx, sqlstore)
authz, err := authzProviderFactory.New(ctx, providerSettings, authz.Config{}) authz, err := authzProviderFactory.New(ctx, providerSettings, authz.Config{})
@@ -287,9 +294,6 @@ func New(
return nil, err return nil, err
} }
// Initialize user getter
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
// Initialize notification manager from the available notification manager provider factories // Initialize notification manager from the available notification manager provider factories
nfManager, err := factory.NewProviderFromNamedMap( nfManager, err := factory.NewProviderFromNamedMap(
ctx, ctx,
@@ -386,9 +390,10 @@ func New(
} }
// Initialize all modules // Initialize all modules
roleModule := implrole.NewModule(implrole.NewStore(sqlstore), authz, nil) roleSetter := roleSetterCallback(sqlstore, authz, licensing, nil)
dashboardModule := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, roleModule, queryParser, querier, licensing) granter := implrole.NewGranter(implrole.NewStore(sqlstore), authz)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboardModule) dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, roleSetter, granter, queryParser, querier, licensing)
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, roleSetter, roleGetter, granter)
// Initialize all handlers for the modules // Initialize all handlers for the modules
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway) handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway)

View File

@@ -24,7 +24,7 @@ var (
typeRoleSelectorRegex = 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}$`)
typeAnonymousSelectorRegex = regexp.MustCompile(`^\*$`) typeAnonymousSelectorRegex = regexp.MustCompile(`^\*$`)
typeOrganizationSelectorRegex = 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}$`)
typeMetaResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`) 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 // metaresources selectors are used to select either all or none
typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`) typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
) )

View File

@@ -20,6 +20,7 @@ var (
ErrCodeInvalidTypeRelation = errors.MustNewCode("role_invalid_type_relation") ErrCodeInvalidTypeRelation = errors.MustNewCode("role_invalid_type_relation")
ErrCodeRoleNotFound = errors.MustNewCode("role_not_found") ErrCodeRoleNotFound = errors.MustNewCode("role_not_found")
ErrCodeRoleFailedTransactionsFromString = errors.MustNewCode("role_failed_transactions_from_string") ErrCodeRoleFailedTransactionsFromString = errors.MustNewCode("role_failed_transactions_from_string")
ErrCodeRoleUnsupported = errors.MustNewCode("role_unsupported")
) )
var ( var (
@@ -32,8 +33,22 @@ var (
) )
var ( var (
AnonymousUserRoleName = "signoz-anonymous" SigNozAnonymousRoleName = "signoz-anonymous"
AnonymousUserRoleDescription = "Role assigned to anonymous users for access to public resources." SigNozAnonymousRoleDescription = "Role assigned to anonymous users for access to public resources."
SigNozAdminRoleName = "signoz-admin"
SigNozAdminRoleDescription = "Role assigned to users who have full administrative access to SigNoz resources."
SigNozEditorRoleName = "signoz-editor"
SigNozEditorRoleDescription = "Role assigned to users who can create, edit, and manage SigNoz resources but do not have full administrative privileges."
SigNozViewerRoleName = "signoz-viewer"
SigNozViewerRoleDescription = "Role assigned to users who have read-only access to SigNoz resources."
)
var (
ExistingRoleToSigNozManagedRoleMap = map[types.Role]string{
types.RoleAdmin: SigNozAdminRoleName,
types.RoleEditor: SigNozEditorRoleName,
types.RoleViewer: SigNozViewerRoleName,
}
) )
var ( var (
@@ -54,10 +69,10 @@ type StorableRole struct {
type Role struct { type Role struct {
types.Identifiable types.Identifiable
types.TimeAuditable types.TimeAuditable
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Type string `json:"type"` Type valuer.String `json:"type"`
OrgID valuer.UUID `json:"org_id"` OrgID valuer.UUID `json:"orgId"`
} }
type PostableRole struct { type PostableRole struct {
@@ -81,7 +96,7 @@ func NewStorableRoleFromRole(role *Role) *StorableRole {
TimeAuditable: role.TimeAuditable, TimeAuditable: role.TimeAuditable,
Name: role.Name, Name: role.Name,
Description: role.Description, Description: role.Description,
Type: role.Type, Type: role.Type.String(),
OrgID: role.OrgID.StringValue(), OrgID: role.OrgID.StringValue(),
} }
} }
@@ -92,12 +107,12 @@ func NewRoleFromStorableRole(storableRole *StorableRole) *Role {
TimeAuditable: storableRole.TimeAuditable, TimeAuditable: storableRole.TimeAuditable,
Name: storableRole.Name, Name: storableRole.Name,
Description: storableRole.Description, Description: storableRole.Description,
Type: storableRole.Type, Type: valuer.NewString(storableRole.Type),
OrgID: valuer.MustNewUUID(storableRole.OrgID), OrgID: valuer.MustNewUUID(storableRole.OrgID),
} }
} }
func NewRole(name, description string, roleType string, orgID valuer.UUID) *Role { func NewRole(name, description string, roleType valuer.String, orgID valuer.UUID) *Role {
return &Role{ return &Role{
Identifiable: types.Identifiable{ Identifiable: types.Identifiable{
ID: valuer.GenerateUUID(), ID: valuer.GenerateUUID(),
@@ -113,7 +128,38 @@ func NewRole(name, description string, roleType string, orgID valuer.UUID) *Role
} }
} }
func NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.Object, relation authtypes.Relation) (*PatchableObjects, error) { func NewManagedRoles(orgID valuer.UUID) []*Role {
return []*Role{
NewRole(SigNozAdminRoleName, SigNozAdminRoleDescription, RoleTypeManaged, orgID),
NewRole(SigNozEditorRoleName, SigNozEditorRoleDescription, RoleTypeManaged, orgID),
NewRole(SigNozViewerRoleName, SigNozViewerRoleDescription, RoleTypeManaged, orgID),
NewRole(SigNozAnonymousRoleName, SigNozAnonymousRoleDescription, RoleTypeManaged, orgID),
}
}
func (role *Role) PatchMetadata(name, description *string) error {
err := role.CanEditDelete()
if err != nil {
return err
}
if name != nil {
role.Name = *name
}
if description != nil {
role.Description = *description
}
role.UpdatedAt = time.Now()
return nil
}
func (role *Role) NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.Object, relation authtypes.Relation) (*PatchableObjects, error) {
err := role.CanEditDelete()
if err != nil {
return nil, err
}
if len(additions) == 0 && len(deletions) == 0 { if len(additions) == 0 && len(deletions) == 0 {
return nil, errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty object patch request received, at least one of additions or deletions must be present") return nil, errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty object patch request received, at least one of additions or deletions must be present")
} }
@@ -133,14 +179,12 @@ func NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.O
return &PatchableObjects{Additions: additions, Deletions: deletions}, nil return &PatchableObjects{Additions: additions, Deletions: deletions}, nil
} }
func (role *Role) PatchMetadata(name, description *string) { func (role *Role) CanEditDelete() error {
if name != nil { if role.Type == RoleTypeManaged {
role.Name = *name return errors.Newf(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "cannot edit/delete managed role: %s", role.Name)
} }
if description != nil {
role.Description = *description return nil
}
role.UpdatedAt = time.Now()
} }
func (role *PostableRole) UnmarshalJSON(data []byte) error { func (role *PostableRole) UnmarshalJSON(data []byte) error {
@@ -246,3 +290,12 @@ func GetDeletionTuples(id valuer.UUID, orgID valuer.UUID, relation authtypes.Rel
return tuples, nil return tuples, nil
} }
func MustGetSigNozManagedRoleFromExistingRole(role types.Role) string {
managedRole, ok := ExistingRoleToSigNozManagedRoleMap[role]
if !ok {
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid role: %s", role.String()))
}
return managedRole
}

View File

@@ -9,8 +9,9 @@ import (
type Store interface { type Store interface {
Create(context.Context, *StorableRole) error Create(context.Context, *StorableRole) error
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableRole, error) Get(context.Context, valuer.UUID, valuer.UUID) (*StorableRole, error)
GetByNameAndOrgID(context.Context, string, valuer.UUID) (*StorableRole, error) GetByOrgIDAndName(context.Context, valuer.UUID, string) (*StorableRole, error)
List(context.Context, valuer.UUID) ([]*StorableRole, error) List(context.Context, valuer.UUID) ([]*StorableRole, error)
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*StorableRole, error)
Update(context.Context, valuer.UUID, *StorableRole) error Update(context.Context, valuer.UUID, *StorableRole) error
Delete(context.Context, valuer.UUID, valuer.UUID) error Delete(context.Context, valuer.UUID, valuer.UUID) error
RunInTx(context.Context, func(ctx context.Context) error) error RunInTx(context.Context, func(ctx context.Context) error) error

View File

@@ -20,6 +20,10 @@ USER_ADMIN_NAME = "admin"
USER_ADMIN_EMAIL = "admin@integration.test" USER_ADMIN_EMAIL = "admin@integration.test"
USER_ADMIN_PASSWORD = "password123Z$" USER_ADMIN_PASSWORD = "password123Z$"
USER_EDITOR_NAME = 'editor'
USER_EDITOR_EMAIL = 'editor@integration.test'
USER_EDITOR_PASSWORD = 'password123Z$'
@pytest.fixture(name="create_user_admin", scope="package") @pytest.fixture(name="create_user_admin", scope="package")
def create_user_admin( def create_user_admin(