Compare commits
11 Commits
main
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
086767b73b | ||
|
|
b185af1aa0 | ||
|
|
03f1541d3e | ||
|
|
ae79796b39 | ||
|
|
dd3bfda87c | ||
|
|
84cb046058 | ||
|
|
d447b1b03c | ||
|
|
a913f52d4d | ||
|
|
a94a10246f | ||
|
|
ff61712458 | ||
|
|
fab8724ebe |
@@ -8,6 +8,9 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaschema"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||
@@ -65,6 +68,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(_ sqlstore.SQLStore, _ zeus.Zeus, _ organization.Getter, _ analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
return nooplicensing.NewFactory()
|
||||
},
|
||||
func(sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
signoz.NewWebProviderFactories(),
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/SigNoz/signoz/cmd"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaauthz"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaschema"
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
@@ -71,6 +74,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter, analytics)
|
||||
},
|
||||
func(sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
signoz.NewWebProviderFactories(),
|
||||
|
||||
@@ -21,12 +21,12 @@ type role
|
||||
define update: [user, role#assignee]
|
||||
define delete: [user, role#assignee]
|
||||
|
||||
type resources
|
||||
type metaresources
|
||||
relations
|
||||
define create: [user, role#assignee]
|
||||
define list: [user, role#assignee]
|
||||
|
||||
type resource
|
||||
type metaresource
|
||||
relations
|
||||
define read: [user, anonymous, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
@@ -35,6 +35,6 @@ type resource
|
||||
define block: [user, role#assignee]
|
||||
|
||||
|
||||
type telemetry
|
||||
type telemetryresource
|
||||
relations
|
||||
define read: [user, anonymous, role#assignee]
|
||||
|
||||
@@ -83,15 +83,24 @@ func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
||||
|
||||
// RegisterRoutes registers routes for this handler on the given router
|
||||
func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// note: add ee override methods first
|
||||
|
||||
// routes available only in ee version
|
||||
// v1
|
||||
router.HandleFunc("/api/v1/features", am.ViewAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||
|
||||
// paid plans specific routes
|
||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.Signoz.Handlers.Session.CreateSessionBySAMLCallback)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/complete/oidc", am.OpenAccess(ah.Signoz.Handlers.Session.CreateSessionByOIDCCallback)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/roles/resources", am.OpenAccess(ah.Signoz.Handlers.Role.GetResources)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/roles", am.OpenAccess(ah.Signoz.Handlers.Role.List)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/roles", am.OpenAccess(ah.Signoz.Handlers.Role.Create)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/roles/{id}", am.OpenAccess(ah.Signoz.Handlers.Role.Get)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/roles/{id}/relation/{relation}/objects", am.OpenAccess(ah.Signoz.Handlers.Role.GetObjects)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/roles/{id}/membership", am.OpenAccess(ah.Signoz.Handlers.Role.GetMembership)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/roles/{id}", am.OpenAccess(ah.Signoz.Handlers.Role.Patch)).Methods(http.MethodPatch)
|
||||
router.HandleFunc("/api/v1/roles/{id}/relation/{relation}/objects", am.OpenAccess(ah.Signoz.Handlers.Role.PatchObjects)).Methods(http.MethodPatch)
|
||||
router.HandleFunc("/api/v1/roles/{id}/membership", am.OpenAccess(ah.Signoz.Handlers.Role.PatchMembership)).Methods(http.MethodPatch)
|
||||
router.HandleFunc("/api/v1/roles/{id}", am.OpenAccess(ah.Signoz.Handlers.Role.Delete)).Methods(http.MethodDelete)
|
||||
|
||||
// base overrides
|
||||
router.HandleFunc("/api/v1/version", am.OpenAccess(ah.getVersion)).Methods(http.MethodGet)
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ func (provider *provider) isModelEqual(expected *openfgav1.AuthorizationModel, a
|
||||
}
|
||||
|
||||
func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKey) error {
|
||||
provider.mtx.RLock()
|
||||
checkResponse, err := provider.openfgaServer.Check(
|
||||
ctx,
|
||||
&openfgav1.CheckRequest{
|
||||
@@ -188,6 +189,7 @@ func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKe
|
||||
Object: tupleReq.Object,
|
||||
},
|
||||
})
|
||||
provider.mtx.RUnlock()
|
||||
if err != nil {
|
||||
return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error())
|
||||
}
|
||||
@@ -211,6 +213,7 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.
|
||||
})
|
||||
}
|
||||
|
||||
provider.mtx.RLock()
|
||||
checkResponse, err := provider.openfgaServer.BatchCheck(
|
||||
ctx,
|
||||
&openfgav1.BatchCheckRequest{
|
||||
@@ -221,6 +224,7 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.
|
||||
if err != nil {
|
||||
return errors.Newf(errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "authorization server is unavailable").WithAdditional(err.Error())
|
||||
}
|
||||
provider.mtx.RUnlock()
|
||||
|
||||
for _, checkResponse := range checkResponse.Result {
|
||||
if checkResponse.GetAllowed() {
|
||||
@@ -257,21 +261,34 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
|
||||
deletionTuplesWithoutCondition[idx] = &openfgav1.TupleKeyWithoutCondition{User: tuple.User, Object: tuple.Object, Relation: tuple.Relation}
|
||||
}
|
||||
|
||||
provider.mtx.RLock()
|
||||
_, err := provider.openfgaServer.Write(ctx, &openfgav1.WriteRequest{
|
||||
StoreId: provider.storeID,
|
||||
AuthorizationModelId: provider.modelID,
|
||||
Writes: &openfgav1.WriteRequestWrites{
|
||||
TupleKeys: additions,
|
||||
},
|
||||
Deletes: &openfgav1.WriteRequestDeletes{
|
||||
TupleKeys: deletionTuplesWithoutCondition,
|
||||
},
|
||||
Writes: func() *openfgav1.WriteRequestWrites {
|
||||
if len(additions) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &openfgav1.WriteRequestWrites{
|
||||
TupleKeys: additions,
|
||||
}
|
||||
}(),
|
||||
Deletes: func() *openfgav1.WriteRequestDeletes {
|
||||
if len(deletionTuplesWithoutCondition) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &openfgav1.WriteRequestDeletes{
|
||||
TupleKeys: deletionTuplesWithoutCondition,
|
||||
}
|
||||
}(),
|
||||
})
|
||||
provider.mtx.RUnlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
provider.mtx.RLock()
|
||||
response, err := provider.openfgaServer.ListObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: provider.storeID,
|
||||
AuthorizationModelId: provider.modelID,
|
||||
@@ -279,6 +296,7 @@ func (provider *provider) ListObjects(ctx context.Context, subject string, relat
|
||||
Relation: relation.StringValue(),
|
||||
Type: typeable.Type().StringValue(),
|
||||
})
|
||||
provider.mtx.RUnlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), typeable.Type().StringValue())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
@@ -13,10 +14,11 @@ type setter struct {
|
||||
store types.OrganizationStore
|
||||
alertmanager alertmanager.Alertmanager
|
||||
quickfilter quickfilter.Module
|
||||
role role.Module
|
||||
}
|
||||
|
||||
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module) organization.Setter {
|
||||
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter}
|
||||
func NewSetter(store types.OrganizationStore, alertmanager alertmanager.Alertmanager, quickfilter quickfilter.Module, role role.Module) organization.Setter {
|
||||
return &setter{store: store, alertmanager: alertmanager, quickfilter: quickfilter, role: role}
|
||||
}
|
||||
|
||||
func (module *setter) Create(ctx context.Context, organization *types.Organization) error {
|
||||
@@ -32,6 +34,10 @@ func (module *setter) Create(ctx context.Context, organization *types.Organizati
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.role.SetManagedRoles(ctx, organization.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ type handler struct {
|
||||
module role.Module
|
||||
}
|
||||
|
||||
func NewHandler(module role.Module) (role.Handler, error) {
|
||||
return &handler{module: module}, nil
|
||||
func NewHandler(module role.Module) role.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -28,25 +28,21 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
req := new(roletypes.PostableRole)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
if err := binding.JSON.BindBody(r.Body, req, binding.WithDisallowUnknownFields(true)); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
role, err := handler.module.Create(ctx, orgID, req.DisplayName, req.Description)
|
||||
err = handler.module.Create(ctx, orgID, req.DisplayName, req.Description)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, role.ID.StringValue())
|
||||
render.Success(rw, http.StatusCreated, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -56,11 +52,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
@@ -79,7 +71,22 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, role)
|
||||
membership, err := handler.module.GetMembership(ctx, orgID, roleID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
userCount := int64(0)
|
||||
for _, mbshp := range membership {
|
||||
switch mbshp.Type {
|
||||
case roletypes.MembershipTypeUser:
|
||||
userCount++
|
||||
}
|
||||
}
|
||||
|
||||
gettableRole := roletypes.NewGettableRoleFromRole(role, &roletypes.Attributes{UserCount: userCount})
|
||||
render.Success(rw, http.StatusOK, gettableRole)
|
||||
}
|
||||
|
||||
func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -89,11 +96,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
@@ -140,40 +143,71 @@ func (handler *handler) GetResources(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Success(rw, http.StatusOK, resourceRelations)
|
||||
}
|
||||
|
||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
func (handler *handler) GetMembership(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := handler.module.List(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, roles)
|
||||
}
|
||||
|
||||
func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
return
|
||||
}
|
||||
roleID, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
memberships, err := handler.module.GetMembership(ctx, valuer.MustNewUUID(claims.OrgID), roleID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, memberships)
|
||||
}
|
||||
|
||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
roles, err := handler.module.List(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
membershipAttributes, err := handler.module.ListMembershipAttributes(ctx, orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
listableRole := make([]*roletypes.GettableRole, len(roles))
|
||||
for idx, role := range roles {
|
||||
listableRole[idx] = roletypes.NewGettableRoleFromRole(role, membershipAttributes[role.ID.String()])
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, listableRole)
|
||||
}
|
||||
|
||||
func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
@@ -187,7 +221,7 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
req := new(roletypes.PatchableRole)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
if err := binding.JSON.BindBody(r.Body, req, binding.WithDisallowUnknownFields(true)); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
@@ -208,11 +242,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
@@ -237,7 +267,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
req := new(roletypes.PatchableObjects)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
if err := binding.JSON.BindBody(r.Body, req, binding.WithDisallowUnknownFields(true)); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
@@ -257,6 +287,40 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Success(rw, http.StatusAccepted, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) PatchMembership(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
|
||||
return
|
||||
}
|
||||
roleID, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(roletypes.PatchableMembership)
|
||||
if err := binding.JSON.BindBody(r.Body, req, binding.WithDisallowUnknownFields(true)); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.PatchMembership(ctx, valuer.MustNewUUID(claims.OrgID), roleID, req.Additions, req.Deletions)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
@@ -264,11 +328,7 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
|
||||
id, ok := mux.Vars(r)["id"]
|
||||
if !ok {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"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"
|
||||
@@ -17,28 +18,22 @@ type module struct {
|
||||
authz authz.AuthZ
|
||||
}
|
||||
|
||||
func NewModule(ctx context.Context, store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) (role.Module, error) {
|
||||
func NewModule(store roletypes.Store, authz authz.AuthZ, registry []role.RegisterTypeable) role.Module {
|
||||
return &module{
|
||||
store: store,
|
||||
authz: authz,
|
||||
registry: registry,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, displayName, description string) (*roletypes.Role, error) {
|
||||
role := roletypes.NewRole(displayName, description, orgID)
|
||||
|
||||
storableRole, err := roletypes.NewStorableRoleFromRole(role)
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, displayName, description string) error {
|
||||
storableRole := roletypes.NewStorableRole(displayName, description, roletypes.RoleTypeCustom, orgID)
|
||||
err := module.store.Create(ctx, storableRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, storableRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
@@ -46,7 +41,7 @@ func (module *module) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
for _, register := range module.registry {
|
||||
typeables = append(typeables, register.MustGetTypeables()...)
|
||||
}
|
||||
// role module cannot self register itself!
|
||||
// role module cannot self register itself hence extracting here.
|
||||
typeables = append(typeables, module.MustGetTypeables()...)
|
||||
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
@@ -72,7 +67,7 @@ func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID
|
||||
}
|
||||
|
||||
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)
|
||||
_, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,7 +79,7 @@ func (module *module) GetObjects(ctx context.Context, orgID valuer.UUID, id valu
|
||||
authz.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeRole, storableRole.ID.String(), authtypes.RelationAssignee),
|
||||
authtypes.MustNewSubject(authtypes.TypeRole, id.String(), authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
@@ -99,6 +94,26 @@ func (module *module) GetObjects(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (module *module) GetMembership(ctx context.Context, orgID valuer.UUID, id valuer.UUID) ([]*roletypes.Membership, error) {
|
||||
_, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storableMembership, err := module.store.GetMembership(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := module.store.ListUserByRole(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
membership := roletypes.NewMembershipFromStorableMembership(storableMembership, users)
|
||||
return membership, nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
storableRoles, err := module.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
@@ -117,6 +132,10 @@ func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (module *module) ListMembershipAttributes(ctx context.Context, orgID valuer.UUID) (map[string]*roletypes.Attributes, error) {
|
||||
return module.store.ListMembershipAttributes(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *module) Patch(ctx context.Context, orgID valuer.UUID, id valuer.UUID, displayName, description *string) error {
|
||||
storableRole, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
@@ -128,7 +147,11 @@ func (module *module) Patch(ctx context.Context, orgID valuer.UUID, id valuer.UU
|
||||
return err
|
||||
}
|
||||
|
||||
role.PatchMetadata(displayName, description)
|
||||
err = role.PatchMetadata(displayName, description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedRole, err := roletypes.NewStorableRoleFromRole(role)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,10 +184,123 @@ func (module *module) PatchObjects(ctx context.Context, orgID valuer.UUID, id va
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) PatchMembership(ctx context.Context, orgID valuer.UUID, id valuer.UUID, additions, deletions []*roletypes.UpdatableMembership) error {
|
||||
_, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
memberships, err := module.store.GetMembership(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatableMemberships := make([]*roletypes.UpdatableMembership, 0)
|
||||
membershipTypeAndIDs := make(map[string]bool)
|
||||
for _, userRole := range memberships.Users {
|
||||
membershipTypeAndIDs[roletypes.MembershipTypeUser.StringValue()+userRole.UserID] = true
|
||||
}
|
||||
|
||||
deletionMap := make(map[string]bool)
|
||||
for _, del := range deletions {
|
||||
switch del.Type {
|
||||
case roletypes.MembershipTypeUser:
|
||||
deletionMap[del.Type.StringValue()+del.UserID.StringValue()] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, userRole := range memberships.Users {
|
||||
if !deletionMap[roletypes.MembershipTypeUser.StringValue()+userRole.UserID] {
|
||||
updatableMemberships = append(updatableMemberships, &roletypes.UpdatableMembership{
|
||||
Type: roletypes.MembershipTypeUser,
|
||||
UserID: valuer.MustNewUUID(userRole.UserID),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, add := range additions {
|
||||
switch add.Type {
|
||||
case roletypes.MembershipTypeUser:
|
||||
if !membershipTypeAndIDs[add.Type.StringValue()+add.UserID.StringValue()] {
|
||||
updatableMemberships = append(updatableMemberships, add)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storableMemberships := roletypes.NewStorableMembershipFromUpdatableMemberships(id, updatableMemberships)
|
||||
err = module.store.UpdateMembership(ctx, id, storableMemberships)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
storableRole, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !role.CanEditOrDelete() {
|
||||
return errors.Newf(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "cannot delete managed role")
|
||||
}
|
||||
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) SetManagedRoles(ctx context.Context, orgID valuer.UUID) error {
|
||||
storableManagedRoleAdmin := roletypes.MustNewStorableRole(
|
||||
roletypes.ManagedRoleSigNozAdminName,
|
||||
roletypes.ManagedRoleSigNozAdminDescription,
|
||||
roletypes.RoleTypeManaged,
|
||||
orgID,
|
||||
)
|
||||
|
||||
storableManagedRoleViewer := roletypes.MustNewStorableRole(
|
||||
roletypes.ManagedRoleSigNozViewerName,
|
||||
roletypes.ManagedRoleSigNozViewerDescription,
|
||||
roletypes.RoleTypeManaged,
|
||||
orgID,
|
||||
)
|
||||
|
||||
storableManagedRoleEditor := roletypes.MustNewStorableRole(
|
||||
roletypes.ManagedRoleSigNozEditorName,
|
||||
roletypes.ManagedRoleSigNozEditorDescription,
|
||||
roletypes.RoleTypeManaged,
|
||||
orgID,
|
||||
)
|
||||
|
||||
err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err := module.store.Create(ctx, storableManagedRoleAdmin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, storableManagedRoleViewer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, storableManagedRoleEditor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -13,14 +15,14 @@ type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) (roletypes.Store, error) {
|
||||
return &store{sqlstore: sqlstore}, nil
|
||||
func NewStore(sqlstore sqlstore.SQLStore) roletypes.Store {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(role).
|
||||
Exec(ctx)
|
||||
@@ -35,10 +37,10 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
role := new(roletypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(role).
|
||||
Where("orgID = ?", orgID).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
@@ -48,26 +50,103 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetMembership(ctx context.Context, id valuer.UUID) (*roletypes.StorableMembership, error) {
|
||||
storableUserMembership := make([]*roletypes.StorableUserRole, 0)
|
||||
err := store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err := store.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&storableUserMembership).
|
||||
Relation("User").
|
||||
Where("role_id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roletypes.MakeStorableMembership(storableUserMembership)
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&roles).
|
||||
Where("orgID = ?", orgID).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "no roles found in org_id: %s", orgID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListMembershipAttributes(ctx context.Context, orgID valuer.UUID) (map[string]*roletypes.Attributes, error) {
|
||||
type resultRow struct {
|
||||
RoleID string `bun:"role_id"`
|
||||
UserCount int64 `bun:"user_count"`
|
||||
}
|
||||
|
||||
var rows []resultRow
|
||||
|
||||
// Count users per role for the org
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model((*roletypes.StorableUserRole)(nil)).
|
||||
Column("role_id").
|
||||
ColumnExpr("count(*) as user_count").
|
||||
Join("JOIN role ON role.id = user_role.role_id").
|
||||
Where("role.org_id = ?", orgID).
|
||||
Group("role_id").
|
||||
Scan(ctx, &rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Map of role_id -> Attributes{UserCount}
|
||||
attrMap := make(map[string]*roletypes.Attributes, len(rows))
|
||||
for _, row := range rows {
|
||||
attrMap[row.RoleID] = &roletypes.Attributes{
|
||||
UserCount: row.UserCount,
|
||||
}
|
||||
}
|
||||
|
||||
return attrMap, nil
|
||||
}
|
||||
|
||||
func (store *store) ListUserByRole(ctx context.Context, id valuer.UUID) ([]*types.User, error) {
|
||||
users := make([]*types.User, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&users).
|
||||
Join("user_role").
|
||||
JoinOn("users.id = user_role.user_id").
|
||||
Where("user_role.role_id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(role).
|
||||
WherePK().
|
||||
@@ -80,10 +159,39 @@ func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletyp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) UpdateMembership(ctx context.Context, id valuer.UUID, storableMembership *roletypes.StorableMembership) error {
|
||||
err := store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
_, err := store.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(roletypes.StorableUserRole)).
|
||||
Where("role_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = store.sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(&storableMembership.Users).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(roletypes.StorableRole)).
|
||||
Where("org_id = ?", orgID).
|
||||
@@ -96,6 +204,21 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) DeleteMembership(ctx context.Context, id valuer.UUID) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(roletypes.StorableUserRole)).
|
||||
Where("role_id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
|
||||
return store.sqlstore.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
|
||||
return cb(ctx)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
type Module interface {
|
||||
// Creates the role metadata
|
||||
Create(context.Context, valuer.UUID, string, string) (*roletypes.Role, error)
|
||||
Create(context.Context, valuer.UUID, string, string) error
|
||||
|
||||
// Gets the role metadata
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
|
||||
@@ -19,9 +19,14 @@ type Module interface {
|
||||
// Gets the objects associated with the given role and relation
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
|
||||
|
||||
// Gets the membership for the given role
|
||||
GetMembership(context.Context, valuer.UUID, valuer.UUID) ([]*roletypes.Membership, error)
|
||||
|
||||
// Lists all the roles metadata for the organization
|
||||
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
|
||||
|
||||
ListMembershipAttributes(context.Context, valuer.UUID) (map[string]*roletypes.Attributes, error)
|
||||
|
||||
// Gets all the typeable resources registered from role registry
|
||||
GetResources(context.Context) []*authtypes.Resource
|
||||
|
||||
@@ -31,9 +36,15 @@ type Module interface {
|
||||
// Patches the objects in authorization server associated with the given role and relation
|
||||
PatchObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation, []*authtypes.Object, []*authtypes.Object) error
|
||||
|
||||
// Patches the membership for the given role
|
||||
PatchMembership(context.Context, valuer.UUID, valuer.UUID, []*roletypes.UpdatableMembership, []*roletypes.UpdatableMembership) error
|
||||
|
||||
// Deletes the role metadata and tuples in authorization server
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
// Initializes the managed roles on creation of the organization
|
||||
SetManagedRoles(context.Context, valuer.UUID) error
|
||||
|
||||
RegisterTypeable
|
||||
}
|
||||
|
||||
@@ -50,11 +61,15 @@ type Handler interface {
|
||||
|
||||
GetResources(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetMembership(http.ResponseWriter, *http.Request)
|
||||
|
||||
List(http.ResponseWriter, *http.Request)
|
||||
|
||||
Patch(http.ResponseWriter, *http.Request)
|
||||
|
||||
PatchObjects(http.ResponseWriter, *http.Request)
|
||||
|
||||
PatchMembership(http.ResponseWriter, *http.Request)
|
||||
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"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/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
@@ -41,6 +43,7 @@ type Handlers struct {
|
||||
AuthDomain authdomain.Handler
|
||||
Session session.Handler
|
||||
SpanPercentile spanpercentile.Handler
|
||||
Role role.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings) Handlers {
|
||||
@@ -57,5 +60,6 @@ func NewHandlers(modules Modules, providerSettings factory.ProviderSettings) Han
|
||||
AuthDomain: implauthdomain.NewHandler(modules.AuthDomain),
|
||||
Session: implsession.NewHandler(modules.Session),
|
||||
SpanPercentile: implspanpercentile.NewHandler(modules.SpanPercentile),
|
||||
Role: implrole.NewHandler(modules.Role),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestNewHandlers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
tokenizer := tokenizertest.New()
|
||||
emailing := emailingtest.New()
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil)
|
||||
|
||||
handlers := NewHandlers(modules, providerSettings)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter/implquickfilter"
|
||||
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
|
||||
"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/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
@@ -52,6 +55,7 @@ type Modules struct {
|
||||
AuthDomain authdomain.Module
|
||||
Session session.Module
|
||||
SpanPercentile spanpercentile.Module
|
||||
Role role.Module
|
||||
}
|
||||
|
||||
func NewModules(
|
||||
@@ -64,19 +68,21 @@ func NewModules(
|
||||
analytics analytics.Analytics,
|
||||
querier querier.Querier,
|
||||
authNs map[authtypes.AuthNProvider]authn.AuthN,
|
||||
authz authz.AuthZ,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
dashboard := impldashboard.NewModule(sqlstore, providerSettings, analytics)
|
||||
role := implrole.NewModule(implrole.NewStore(sqlstore), authz, []role.RegisterTypeable{dashboard})
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter, role)
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, analytics)
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
|
||||
return Modules{
|
||||
OrgGetter: orgGetter,
|
||||
OrgSetter: orgSetter,
|
||||
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()),
|
||||
SavedView: implsavedview.NewModule(sqlstore),
|
||||
Apdex: implapdex.NewModule(sqlstore),
|
||||
Dashboard: impldashboard.NewModule(sqlstore, providerSettings, analytics),
|
||||
Dashboard: dashboard,
|
||||
User: user,
|
||||
UserGetter: userGetter,
|
||||
QuickFilter: quickfilter,
|
||||
@@ -85,5 +91,6 @@ func NewModules(
|
||||
AuthDomain: implauthdomain.NewModule(implauthdomain.NewStore(sqlstore)),
|
||||
Session: implsession.NewModule(providerSettings, authNs, user, userGetter, implauthdomain.NewModule(implauthdomain.NewStore(sqlstore)), tokenizer, orgGetter),
|
||||
SpanPercentile: implspanpercentile.NewModule(querier, providerSettings),
|
||||
Role: role,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestNewModules(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
tokenizer := tokenizertest.New()
|
||||
emailing := emailingtest.New()
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -138,6 +138,8 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewUpdateTTLSettingForCustomRetentionFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddRoutePolicyFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddAuthTokenFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddRoleFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewOpenfgaMigrationFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/analytics"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authn/authnstore/sqlauthnstore"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -62,6 +63,7 @@ func New(
|
||||
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
|
||||
licenseConfig licensing.Config,
|
||||
licenseProviderFactory func(sqlstore.SQLStore, zeus.Zeus, organization.Getter, analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config],
|
||||
authzProviderFactoryCb func(sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config],
|
||||
emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]],
|
||||
cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]],
|
||||
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
|
||||
@@ -299,8 +301,15 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize authz
|
||||
authzProviderFactory := authzProviderFactoryCb(sqlstore)
|
||||
authz, err := authzProviderFactory.New(ctx, providerSettings, authz.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, authNs)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, authNs, authz)
|
||||
|
||||
// Initialize all handlers for the modules
|
||||
handlers := NewHandlers(modules, providerSettings)
|
||||
@@ -337,6 +346,7 @@ func New(
|
||||
factory.NewNamedService(factory.MustNewName("licensing"), licensing),
|
||||
factory.NewNamedService(factory.MustNewName("statsreporter"), statsReporter),
|
||||
factory.NewNamedService(factory.MustNewName("tokenizer"), tokenizer),
|
||||
factory.NewNamedService(factory.MustNewName("authz"), authz),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
193
pkg/sqlmigration/051_add_role.go
Normal file
193
pkg/sqlmigration/051_add_role.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addRole struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddRoleFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_role"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return newAddRole(ctx, providerSettings, config, sqlstore, sqlschema)
|
||||
})
|
||||
}
|
||||
|
||||
func newAddRole(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) {
|
||||
return &addRole{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (migration *addRole) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addRole) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sqls := [][]byte{}
|
||||
tableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "role",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "display_name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "description", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
{Name: "type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"id"},
|
||||
},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("org_id"),
|
||||
ReferencedTableName: sqlschema.TableName("organizations"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "role", ColumnNames: []sqlschema.ColumnName{"display_name", "org_id"}})
|
||||
sqls = append(sqls, indexSQLs...)
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "user_role",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "role_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"id"},
|
||||
},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("role_id"),
|
||||
ReferencedTableName: sqlschema.TableName("role"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("user_id"),
|
||||
ReferencedTableName: sqlschema.TableName("users"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
indexSQLs = migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "user_role", ColumnNames: []sqlschema.ColumnName{"role_id", "user_id"}})
|
||||
sqls = append(sqls, indexSQLs...)
|
||||
|
||||
for _, sqlStmt := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sqlStmt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var orgIDs []string
|
||||
err = tx.NewSelect().
|
||||
Table("organizations").
|
||||
Column("id").
|
||||
Scan(ctx, &orgIDs)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
type storableRole struct {
|
||||
bun.BaseModel `bun:"table:role"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
DisplayName string `bun:"display_name,type:string"`
|
||||
Description string `bun:"description,type:string"`
|
||||
Type string `bun:"type,type:string"`
|
||||
OrgID string `bun:"org_id,type:string"`
|
||||
}
|
||||
|
||||
for _, orgID := range orgIDs {
|
||||
roles := []storableRole{
|
||||
{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: "SigNoz Admin",
|
||||
Description: "Default SigNoz Admin with all the permissions",
|
||||
Type: "managed",
|
||||
OrgID: orgID,
|
||||
},
|
||||
{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: "SigNoz Editor",
|
||||
Description: "Default SigNoz Editor with certain write permission",
|
||||
Type: "managed",
|
||||
OrgID: orgID,
|
||||
},
|
||||
{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: "SigNoz Viewer",
|
||||
Description: "Org member role with permissions to view and collaborate",
|
||||
Type: "managed",
|
||||
OrgID: orgID,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := tx.NewInsert().
|
||||
Model(&roles).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addRole) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
159
pkg/sqlmigration/052_openfga_migration.go
Normal file
159
pkg/sqlmigration/052_openfga_migration.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type openfgaMigraion struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewOpenfgaMigrationFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("openfga_migration"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return newOpenfgaMigration(ctx, providerSettings, config, sqlstore, sqlschema)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaMigration(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) {
|
||||
return &openfgaMigraion{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (migration *openfgaMigraion) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *openfgaMigraion) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sqls := [][]byte{}
|
||||
tableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "tuple",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "store", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "object_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "object_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_object_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "relation", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_object_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_relation", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "ulid", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "inserted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "condition_name", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
//todo check why openfga uses long blob for this
|
||||
{Name: "condition_context", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"store", "object_type", "object_id", "relation", "user_object_type", "user_object_id", "user_relation"},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "tuple", ColumnNames: []sqlschema.ColumnName{"ulid"}})
|
||||
sqls = append(sqls, indexSQLs...)
|
||||
|
||||
// todo: need to create this indexes
|
||||
// CREATE INDEX idx_reverse_lookup_user ON tuple (store, object_type, relation, user_object_type, user_object_id, user_relation);
|
||||
// CREATE INDEX idx_tuple_partial_user ON tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) WHERE user_type = 'user';
|
||||
// CREATE INDEX idx_tuple_partial_userset ON tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) WHERE user_type = 'userset';
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "authorization_model",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "store", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "authorization_model_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "schema_version", DataType: sqlschema.DataTypeText, Nullable: false, Default: "1.1"},
|
||||
{Name: "serialized_protobuf", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"store", "authorization_model_id"},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "store",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: true},
|
||||
{Name: "deleted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: true},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"id"},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "assertion",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "store", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "authorization_model_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "assertions", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"store", "authorization_model_id"},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "changelog",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "store", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "object_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "object_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "relation", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_object_type", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_object_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_relation", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "operation", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "ulid", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "inserted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "condition_name", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
{Name: "condition_context", DataType: sqlschema.DataTypeText, Nullable: true},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"store", "object_type", "ulid"},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
for _, sqlStmt := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sqlStmt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *openfgaMigraion) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -11,6 +11,11 @@ var (
|
||||
nameRegex = regexp.MustCompile("^[a-z]{1,35}$")
|
||||
)
|
||||
|
||||
var (
|
||||
_ json.Marshaler = new(Name)
|
||||
_ json.Unmarshaler = new(Name)
|
||||
)
|
||||
|
||||
type Name struct {
|
||||
val string
|
||||
}
|
||||
@@ -36,6 +41,10 @@ func (name Name) String() string {
|
||||
return name.val
|
||||
}
|
||||
|
||||
func (name *Name) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(name.val)
|
||||
}
|
||||
|
||||
func (name *Name) UnmarshalJSON(data []byte) error {
|
||||
nameStr := ""
|
||||
err := json.Unmarshal(data, &nameStr)
|
||||
|
||||
@@ -20,11 +20,11 @@ var (
|
||||
)
|
||||
|
||||
var TypeableRelations = map[Type][]Relation{
|
||||
TypeUser: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeOrganization: {RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList},
|
||||
TypeResource: {RelationRead, RelationUpdate, RelationDelete, RelationBlock},
|
||||
TypeResources: {RelationCreate, RelationList},
|
||||
TypeUser: {RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeRole: {RelationAssignee, RelationRead, RelationUpdate, RelationDelete},
|
||||
TypeOrganization: {RelationCreate, RelationRead, RelationUpdate, RelationDelete, RelationList},
|
||||
TypeMetaResource: {RelationRead, RelationUpdate, RelationDelete, RelationBlock},
|
||||
TypeMetaResources: {RelationCreate, RelationList},
|
||||
}
|
||||
|
||||
type Relation struct{ valuer.String }
|
||||
|
||||
@@ -9,16 +9,21 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAuthZInvalidSelectorRegex = errors.MustNewCode("authz_invalid_selector_regex")
|
||||
ErrCodeAuthZInvalidSelector = errors.MustNewCode("authz_invalid_selector")
|
||||
)
|
||||
|
||||
var (
|
||||
_ json.Marshaler = new(Selector)
|
||||
_ json.Unmarshaler = new(Selector)
|
||||
)
|
||||
|
||||
var (
|
||||
typeUserSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeRoleSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeOrganizationSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
// resources selectors are used to select either all or none
|
||||
typeResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
typeMetaResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
// metaresources selectors are used to select either all or none
|
||||
typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
)
|
||||
|
||||
type SelectorCallbackFn func(context.Context, Claims) ([]Selector, error)
|
||||
@@ -36,33 +41,6 @@ func NewSelector(typed Type, selector string) (Selector, error) {
|
||||
return Selector{val: selector}, nil
|
||||
}
|
||||
|
||||
func IsValidSelector(typed Type, selector string) error {
|
||||
switch typed {
|
||||
case TypeUser:
|
||||
if !typeUserSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeUserSelectorRegex.String())
|
||||
}
|
||||
case TypeRole:
|
||||
if !typeRoleSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeRoleSelectorRegex.String())
|
||||
}
|
||||
case TypeOrganization:
|
||||
if !typeOrganizationSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeOrganizationSelectorRegex.String())
|
||||
}
|
||||
case TypeResource:
|
||||
if !typeResourceSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourceSelectorRegex.String())
|
||||
}
|
||||
case TypeResources:
|
||||
if !typeResourcesSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelectorRegex, "selector must conform to regex %s", typeResourcesSelectorRegex.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MustNewSelector(typed Type, input string) Selector {
|
||||
selector, err := NewSelector(typed, input)
|
||||
if err != nil {
|
||||
@@ -76,6 +54,10 @@ func (selector Selector) String() string {
|
||||
return selector.val
|
||||
}
|
||||
|
||||
func (selector *Selector) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(selector.val)
|
||||
}
|
||||
|
||||
func (typed *Selector) UnmarshalJSON(data []byte) error {
|
||||
str := ""
|
||||
err := json.Unmarshal(data, &str)
|
||||
@@ -83,8 +65,35 @@ func (typed *Selector) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
shadow := Selector{val: str}
|
||||
*typed = shadow
|
||||
alias := Selector{val: str}
|
||||
*typed = alias
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsValidSelector(typed Type, selector string) error {
|
||||
switch typed {
|
||||
case TypeUser:
|
||||
if !typeUserSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeUserSelectorRegex.String())
|
||||
}
|
||||
case TypeRole:
|
||||
if !typeRoleSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeRoleSelectorRegex.String())
|
||||
}
|
||||
case TypeOrganization:
|
||||
if !typeOrganizationSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeOrganizationSelectorRegex.String())
|
||||
}
|
||||
case TypeMetaResource:
|
||||
if !typeMetaResourceSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourceSelectorRegex.String())
|
||||
}
|
||||
case TypeMetaResources:
|
||||
if !typeMetaResourcesSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeMetaResourcesSelectorRegex.String())
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", typed)
|
||||
}
|
||||
|
||||
@@ -33,22 +33,24 @@ func NewObject(resource Resource, selector Selector) (*Object, error) {
|
||||
}
|
||||
|
||||
func MustNewObjectFromString(input string) *Object {
|
||||
parts := strings.Split(input, ":")
|
||||
if len(parts) != 3 {
|
||||
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid list objects output: %s", input))
|
||||
parts := strings.Split(input, "/")
|
||||
if len(parts) != 4 {
|
||||
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid input format: %s", input))
|
||||
}
|
||||
|
||||
typeParts := strings.Split(parts[0], ":")
|
||||
if len(typeParts) != 2 {
|
||||
panic(errors.Newf(errors.TypeInternal, errors.CodeInternal, "invalid type format: %s", parts[0]))
|
||||
}
|
||||
|
||||
resource := Resource{
|
||||
Type: MustNewType(parts[0]),
|
||||
Name: MustNewName(parts[1]),
|
||||
Type: MustNewType(typeParts[0]),
|
||||
Name: MustNewName(parts[2]),
|
||||
}
|
||||
|
||||
object := &Object{
|
||||
Resource: resource,
|
||||
Selector: MustNewSelector(resource.Type, parts[2]),
|
||||
}
|
||||
selector := MustNewSelector(resource.Type, parts[3])
|
||||
|
||||
return object
|
||||
return &Object{Resource: resource, Selector: selector}
|
||||
}
|
||||
|
||||
func MustNewObjectsFromStringSlice(input []string) []*Object {
|
||||
@@ -59,6 +61,14 @@ func MustNewObjectsFromStringSlice(input []string) []*Object {
|
||||
return objects
|
||||
}
|
||||
|
||||
func NewTransaction(relation Relation, object Object) (*Transaction, error) {
|
||||
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||
}
|
||||
|
||||
return &Transaction{Relation: relation, Object: object}, nil
|
||||
}
|
||||
|
||||
func (object *Object) UnmarshalJSON(data []byte) error {
|
||||
var shadow = struct {
|
||||
Resource Resource
|
||||
@@ -79,14 +89,6 @@ func (object *Object) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTransaction(relation Relation, object Object) (*Transaction, error) {
|
||||
if !slices.Contains(TypeableRelations[object.Resource.Type], relation) {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidRelation, "invalid relation %s for type %s", relation.StringValue(), object.Resource.Type.StringValue())
|
||||
}
|
||||
|
||||
return &Transaction{Relation: relation, Object: object}, nil
|
||||
}
|
||||
|
||||
func (transaction *Transaction) UnmarshalJSON(data []byte) error {
|
||||
var shadow = struct {
|
||||
Relation Relation
|
||||
|
||||
@@ -11,14 +11,15 @@ import (
|
||||
var (
|
||||
ErrCodeAuthZUnavailable = errors.MustNewCode("authz_unavailable")
|
||||
ErrCodeAuthZForbidden = errors.MustNewCode("authz_forbidden")
|
||||
ErrCodeAuthZInvalidType = errors.MustNewCode("authz_invalid_type")
|
||||
)
|
||||
|
||||
var (
|
||||
TypeUser = Type{valuer.NewString("user")}
|
||||
TypeRole = Type{valuer.NewString("role")}
|
||||
TypeOrganization = Type{valuer.NewString("organization")}
|
||||
TypeResource = Type{valuer.NewString("resource")}
|
||||
TypeResources = Type{valuer.NewString("resources")}
|
||||
TypeUser = Type{valuer.NewString("user")}
|
||||
TypeRole = Type{valuer.NewString("role")}
|
||||
TypeOrganization = Type{valuer.NewString("organization")}
|
||||
TypeMetaResource = Type{valuer.NewString("metaresource")}
|
||||
TypeMetaResources = Type{valuer.NewString("metaresources")}
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -53,12 +54,12 @@ func NewType(input string) (Type, error) {
|
||||
return TypeRole, nil
|
||||
case "organization":
|
||||
return TypeOrganization, nil
|
||||
case "resource":
|
||||
return TypeResource, nil
|
||||
case "resources":
|
||||
return TypeResources, nil
|
||||
case "metaresource":
|
||||
return TypeMetaResource, nil
|
||||
case "metaresources":
|
||||
return TypeMetaResources, nil
|
||||
default:
|
||||
return Type{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid type: %s", input)
|
||||
return Type{}, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type: %s", input)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +70,12 @@ func (typed *Type) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
shadow, err := NewType(str)
|
||||
alias, err := NewType(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*typed = shadow
|
||||
*typed = alias
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -86,21 +87,21 @@ func NewTypeableFromType(typed Type, name Name) (Typeable, error) {
|
||||
return TypeableUser, nil
|
||||
case TypeOrganization:
|
||||
return TypeableOrganization, nil
|
||||
case TypeResource:
|
||||
resource, err := NewTypeableResource(name)
|
||||
case TypeMetaResource:
|
||||
resource, err := NewTypeableMetaResource(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resource, nil
|
||||
case TypeResources:
|
||||
resources, err := NewTypeableResources(name)
|
||||
case TypeMetaResources:
|
||||
resources, err := NewTypeableMetaResources(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid type")
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidType, "invalid type %s", typed)
|
||||
}
|
||||
|
||||
func MustNewTypeableFromType(typed Type, name Name) Typeable {
|
||||
|
||||
47
pkg/types/authtypes/typeable_metaresource.go
Normal file
47
pkg/types/authtypes/typeable_metaresource.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableMetaResource)
|
||||
|
||||
type typeableMetaResource struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableMetaResource(name Name) (Typeable, error) {
|
||||
return &typeableMetaResource{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableMetaResource(name Name) Typeable {
|
||||
typeableesource, err := NewTypeableMetaResource(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return typeableesource
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableMetaResource.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Type() Type {
|
||||
return TypeMetaResource
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Name() Name {
|
||||
return typeableMetaResource.name
|
||||
}
|
||||
|
||||
func (typeableMetaResource *typeableMetaResource) Prefix(orgID valuer.UUID) string {
|
||||
// example: metaresource:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboard
|
||||
return typeableMetaResource.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResource.Name().String()
|
||||
}
|
||||
47
pkg/types/authtypes/typeable_metaresources.go
Normal file
47
pkg/types/authtypes/typeable_metaresources.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableMetaResources)
|
||||
|
||||
type typeableMetaResources struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableMetaResources(name Name) (Typeable, error) {
|
||||
return &typeableMetaResources{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableMetaResources(name Name) Typeable {
|
||||
resources, err := NewTypeableMetaResources(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func (typeableResources *typeableMetaResources) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableResources.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Type() Type {
|
||||
return TypeMetaResources
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Name() Name {
|
||||
return typeableMetaResources.name
|
||||
}
|
||||
|
||||
func (typeableMetaResources *typeableMetaResources) Prefix(orgID valuer.UUID) string {
|
||||
// example: metaresources:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboards
|
||||
return typeableMetaResources.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableMetaResources.Name().String()
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableResource)
|
||||
|
||||
type typeableResource struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableResource(name Name) (Typeable, error) {
|
||||
return &typeableResource{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableResource(name Name) Typeable {
|
||||
typeableesource, err := NewTypeableResource(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return typeableesource
|
||||
}
|
||||
|
||||
func (typeableResource *typeableResource) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableResource.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableResource *typeableResource) Type() Type {
|
||||
return TypeResource
|
||||
}
|
||||
|
||||
func (typeableResource *typeableResource) Name() Name {
|
||||
return typeableResource.name
|
||||
}
|
||||
|
||||
func (typeableResource *typeableResource) Prefix(orgID valuer.UUID) string {
|
||||
// example: resource:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboard
|
||||
return typeableResource.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableResource.Name().String()
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableResources)
|
||||
|
||||
type typeableResources struct {
|
||||
name Name
|
||||
}
|
||||
|
||||
func NewTypeableResources(name Name) (Typeable, error) {
|
||||
return &typeableResources{name: name}, nil
|
||||
}
|
||||
|
||||
func MustNewTypeableResources(name Name) Typeable {
|
||||
resources, err := NewTypeableResources(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func (typeableResources *typeableResources) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableResources.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableResources *typeableResources) Type() Type {
|
||||
return TypeResources
|
||||
}
|
||||
|
||||
func (typeableResources *typeableResources) Name() Name {
|
||||
return typeableResources.name
|
||||
}
|
||||
|
||||
func (typeableResources *typeableResources) Prefix(orgID valuer.UUID) string {
|
||||
// example: resources:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/dashboards
|
||||
return typeableResources.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableResources.Name().String()
|
||||
}
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourceDashboard = authtypes.MustNewTypeableResource(authtypes.MustNewName("dashboard"))
|
||||
TypeableResourcesDashboards = authtypes.MustNewTypeableResources(authtypes.MustNewName("dashboards"))
|
||||
TypeableResourceDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("dashboard"))
|
||||
TypeableResourcesDashboards = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("dashboards"))
|
||||
)
|
||||
|
||||
type StorableDashboard struct {
|
||||
|
||||
@@ -19,10 +19,29 @@ var (
|
||||
ErrCodeInvalidTypeRelation = errors.MustNewCode("role_invalid_type_relation")
|
||||
ErrCodeRoleNotFound = errors.MustNewCode("role_not_found")
|
||||
ErrCodeRoleFailedTransactionsFromString = errors.MustNewCode("role_failed_transactions_from_string")
|
||||
ErrCodeRoleInvalidMembershipType = errors.MustNewCode("role_invalid_membership_type")
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourcesRoles = authtypes.MustNewTypeableResources(authtypes.MustNewName("roles"))
|
||||
MembershipTypeUser = valuer.NewString("user")
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourcesRoles = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("roles"))
|
||||
)
|
||||
|
||||
var (
|
||||
RoleTypeManaged = valuer.NewString("managed")
|
||||
RoleTypeCustom = valuer.NewString("custom")
|
||||
|
||||
ManagedRoleSigNozAdminName = "SigNoz Admin"
|
||||
ManagedRoleSigNozAdminDescription = "Default SigNoz Admin with all the permissions"
|
||||
|
||||
ManagedRoleSigNozEditorName = "SigNoz Editor"
|
||||
ManagedRoleSigNozEditorDescription = "Default SigNoz Editor with certain write permission"
|
||||
|
||||
ManagedRoleSigNozViewerName = "SigNoz Viewer"
|
||||
ManagedRoleSigNozViewerDescription = "Org member role with permissions to view and collaborate"
|
||||
)
|
||||
|
||||
type StorableRole struct {
|
||||
@@ -32,15 +51,52 @@ type StorableRole struct {
|
||||
types.TimeAuditable
|
||||
DisplayName string `bun:"display_name,type:string"`
|
||||
Description string `bun:"description,type:string"`
|
||||
Type string `bun:"type,type:boolean"`
|
||||
OrgID string `bun:"org_id,type:string"`
|
||||
}
|
||||
|
||||
type StorableUserRole struct {
|
||||
bun.BaseModel `bun:"table:user_role"`
|
||||
|
||||
types.Identifiable
|
||||
RoleID string `bun:"role_id,type:text"`
|
||||
UserID string `bun:"user_id,type:text"`
|
||||
}
|
||||
|
||||
type StorableMembership struct {
|
||||
Users []*StorableUserRole
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
OrgID valuer.UUID `json:"org_id"`
|
||||
Type string `json:"type"`
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
}
|
||||
|
||||
type GettableRole struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
OrgID valuer.UUID `json:"orgId"`
|
||||
Attributes *Attributes `json:"attributes"`
|
||||
}
|
||||
|
||||
type Attributes struct {
|
||||
UserCount int64 `json:"user_count"`
|
||||
}
|
||||
|
||||
type Membership struct {
|
||||
Type valuer.String `json:"type"`
|
||||
User *types.User `json:"user"`
|
||||
}
|
||||
type UpdatableMembership struct {
|
||||
Type valuer.String `json:"type"`
|
||||
UserID valuer.UUID `json:"userId"`
|
||||
}
|
||||
|
||||
type PostableRole struct {
|
||||
@@ -58,32 +114,66 @@ type PatchableObjects struct {
|
||||
Deletions []*authtypes.Object `json:"deletions"`
|
||||
}
|
||||
|
||||
type PatchableMembership struct {
|
||||
Additions []*UpdatableMembership `json:"additions"`
|
||||
Deletions []*UpdatableMembership `json:"deletions"`
|
||||
}
|
||||
|
||||
func NewStorableRole(displayName, description string, roleType valuer.String, orgID valuer.UUID) *StorableRole {
|
||||
return &StorableRole{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
Type: roleType.StringValue(),
|
||||
OrgID: orgID.StringValue(),
|
||||
}
|
||||
}
|
||||
|
||||
func MustNewStorableRole(displayName, description string, roleType valuer.String, orgID valuer.UUID) *StorableRole {
|
||||
return &StorableRole{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
Type: roleType.StringValue(),
|
||||
OrgID: orgID.StringValue(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewStorableRoleFromRole(role *Role) (*StorableRole, error) {
|
||||
return &StorableRole{
|
||||
Identifiable: role.Identifiable,
|
||||
TimeAuditable: role.TimeAuditable,
|
||||
DisplayName: role.DisplayName,
|
||||
Description: role.Description,
|
||||
Type: role.Type,
|
||||
OrgID: role.OrgID.StringValue(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRoleFromStorableRole(storableRole *StorableRole) (*Role, error) {
|
||||
orgID, err := valuer.NewUUID(storableRole.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Role{
|
||||
Identifiable: storableRole.Identifiable,
|
||||
TimeAuditable: storableRole.TimeAuditable,
|
||||
DisplayName: storableRole.DisplayName,
|
||||
Description: storableRole.Description,
|
||||
OrgID: orgID,
|
||||
Type: storableRole.Type,
|
||||
OrgID: valuer.MustNewUUID(storableRole.OrgID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewRole(displayName, description string, orgID valuer.UUID) *Role {
|
||||
func NewRole(displayName, description string, roleType valuer.String, orgID valuer.UUID) *Role {
|
||||
return &Role{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
@@ -94,13 +184,27 @@ func NewRole(displayName, description string, orgID valuer.UUID) *Role {
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
Type: roleType.StringValue(),
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGettableRoleFromRole(role *Role, attributes *Attributes) *GettableRole {
|
||||
|
||||
return &GettableRole{
|
||||
Identifiable: role.Identifiable,
|
||||
TimeAuditable: role.TimeAuditable,
|
||||
DisplayName: role.DisplayName,
|
||||
Description: role.Description,
|
||||
Type: role.Type,
|
||||
OrgID: role.OrgID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
|
||||
func NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.Object, relation authtypes.Relation) (*PatchableObjects, error) {
|
||||
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 patch objects request received, at least one of additions or deletions must be present")
|
||||
}
|
||||
|
||||
for _, object := range additions {
|
||||
@@ -118,7 +222,53 @@ func NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.O
|
||||
return &PatchableObjects{Additions: additions, Deletions: deletions}, nil
|
||||
}
|
||||
|
||||
func (role *Role) PatchMetadata(displayName, description *string) {
|
||||
func NewMembershipFromStorableMembership(storableMembership *StorableMembership, users []*types.User) []*Membership {
|
||||
usersMap := make(map[string]*types.User)
|
||||
for _, user := range users {
|
||||
usersMap[user.ID.String()] = user
|
||||
}
|
||||
|
||||
membership := make([]*Membership, 0)
|
||||
for _, userRole := range storableMembership.Users {
|
||||
membership = append(membership, &Membership{
|
||||
Type: MembershipTypeUser,
|
||||
User: usersMap[userRole.UserID],
|
||||
})
|
||||
}
|
||||
|
||||
return membership
|
||||
}
|
||||
|
||||
func NewStorableMembershipFromUpdatableMemberships(id valuer.UUID, updatableMemberships []*UpdatableMembership) *StorableMembership {
|
||||
userMemberships := make([]*StorableUserRole, 0)
|
||||
for _, membership := range updatableMemberships {
|
||||
switch membership.Type {
|
||||
case MembershipTypeUser:
|
||||
userMemberships = append(userMemberships, &StorableUserRole{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
UserID: membership.UserID.StringValue(),
|
||||
RoleID: id.StringValue(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &StorableMembership{Users: userMemberships}
|
||||
}
|
||||
|
||||
func MakeStorableMembership(users []*StorableUserRole) (*StorableMembership, error) {
|
||||
storableMembership := new(StorableMembership)
|
||||
storableMembership.Users = users
|
||||
|
||||
return storableMembership, nil
|
||||
}
|
||||
|
||||
func (role *Role) PatchMetadata(displayName, description *string) error {
|
||||
if !role.CanEditOrDelete() {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "cannot patch managed roles")
|
||||
}
|
||||
|
||||
if displayName != nil {
|
||||
role.DisplayName = *displayName
|
||||
}
|
||||
@@ -126,6 +276,12 @@ func (role *Role) PatchMetadata(displayName, description *string) {
|
||||
role.Description = *description
|
||||
}
|
||||
role.UpdatedAt = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (role *Role) CanEditOrDelete() bool {
|
||||
return role.Type == RoleTypeCustom.StringValue()
|
||||
}
|
||||
|
||||
func (role *PostableRole) UnmarshalJSON(data []byte) error {
|
||||
@@ -161,7 +317,11 @@ func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
if shadowRole.DisplayName == nil && shadowRole.Description == nil {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty role patch request received, at least one of displayName or description must be present")
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty patch role request received, at least one of displayName or description must be present")
|
||||
}
|
||||
|
||||
if shadowRole.DisplayName != nil && *shadowRole.DisplayName == "" {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "cannot set empty displayName for the role")
|
||||
}
|
||||
|
||||
role.DisplayName = shadowRole.DisplayName
|
||||
@@ -170,6 +330,22 @@ func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (request *UpdatableMembership) UnmarshalJSON(data []byte) error {
|
||||
type Alias UpdatableMembership
|
||||
|
||||
var temp Alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if temp.Type != MembershipTypeUser {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeRoleInvalidMembershipType, "invalid membership type, accepted values are :%s", MembershipTypeUser)
|
||||
}
|
||||
|
||||
*request = UpdatableMembership(temp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAdditionTuples(id valuer.UUID, orgID valuer.UUID, relation authtypes.Relation, additions []*authtypes.Object) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
|
||||
@@ -3,14 +3,20 @@ package roletypes
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
Create(context.Context, *StorableRole) error
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableRole, error)
|
||||
GetMembership(context.Context, valuer.UUID) (*StorableMembership, error)
|
||||
List(context.Context, valuer.UUID) ([]*StorableRole, error)
|
||||
ListMembershipAttributes(context.Context, valuer.UUID) (map[string]*Attributes, error)
|
||||
ListUserByRole(context.Context, valuer.UUID) ([]*types.User, error)
|
||||
Update(context.Context, valuer.UUID, *StorableRole) error
|
||||
UpdateMembership(context.Context, valuer.UUID, *StorableMembership) error
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
DeleteMembership(context.Context, valuer.UUID) error
|
||||
RunInTx(context.Context, func(ctx context.Context) error) error
|
||||
}
|
||||
|
||||
@@ -103,3 +103,7 @@ func (enum *Email) UnmarshalText(text []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enum Email) MarshalText() (text []byte, err error) {
|
||||
return []byte(enum.StringValue()), nil
|
||||
}
|
||||
|
||||
@@ -73,3 +73,7 @@ func (enum *String) UnmarshalText(text []byte) error {
|
||||
*enum = NewString(string(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enum String) MarshalText() (text []byte, err error) {
|
||||
return []byte(enum.StringValue()), nil
|
||||
}
|
||||
|
||||
@@ -136,3 +136,7 @@ func (enum *UUID) UnmarshalText(text []byte) error {
|
||||
*enum = uuid
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enum UUID) MarshalText() (text []byte, err error) {
|
||||
return []byte(enum.StringValue()), nil
|
||||
}
|
||||
|
||||
@@ -39,4 +39,7 @@ type Valuer interface {
|
||||
|
||||
// Implement encoding.TextUnmarshaler to allow the value to be unmarshalled from a string
|
||||
encoding.TextUnmarshaler
|
||||
|
||||
// Implement encoding.TextUnmarshaler to allow the value to be marshalled unto a string
|
||||
encoding.TextMarshaler
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user