feat(authz): publicly shareable dashboards (#9584)
* feat(authz): base setup for public shareable dashboards * feat(authz): add support for public masking * feat(authz): added public path for gettable public dashboard * feat(authz): checkpoint-1 for widget query to query range conversion * feat(authz): checkpoint-2 for widget query to query range conversion * feat(authz): fix widget index issue * feat(authz): better handling for dashboard json and query * feat(authz): use the default time range if timerange is disabled * feat(authz): use the default time range if timerange is disabled * feat(authz): add authz changes * feat(authz): integrate role with dashboard anonymous access * feat(authz): integrate the new middleware * feat(authz): integrate the new middleware * feat(authz): add back licensing * feat(authz): renaming selector callback * feat(authz): self review * feat(authz): self review * feat(authz): change to promql
This commit is contained in:
@@ -5,9 +5,12 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/cmd"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaauthz"
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaschema"
|
||||
"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/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||
@@ -76,6 +79,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", "error", err)
|
||||
|
||||
@@ -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"
|
||||
@@ -105,6 +108,9 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return authNs, nil
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", "error", err)
|
||||
|
||||
@@ -48,7 +48,26 @@ func (provider *provider) Check(ctx context.Context, tuple *openfgav1.TupleKey)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeUser, claims.UserID, authtypes.Relation{})
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, _ authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type anonymous
|
||||
|
||||
type role
|
||||
relations
|
||||
define assignee: [user]
|
||||
define assignee: [user, anonymous]
|
||||
|
||||
define read: [user, role#assignee]
|
||||
define update: [user, role#assignee]
|
||||
@@ -37,4 +37,4 @@ type metaresource
|
||||
|
||||
type telemetryresource
|
||||
relations
|
||||
define read: [user, anonymous, role#assignee]
|
||||
define read: [user, role#assignee]
|
||||
|
||||
@@ -20,6 +20,10 @@ import (
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -99,6 +103,39 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost)
|
||||
|
||||
// dashboards
|
||||
router.HandleFunc("/api/v1/dashboards/{id}/public", am.AdminAccess(ah.Signoz.Handlers.Dashboard.CreatePublic)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}/public", am.AdminAccess(ah.Signoz.Handlers.Dashboard.GetPublic)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}/public", am.AdminAccess(ah.Signoz.Handlers.Dashboard.UpdatePublic)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/dashboards/{id}/public", am.AdminAccess(ah.Signoz.Handlers.Dashboard.DeletePublic)).Methods(http.MethodDelete)
|
||||
|
||||
// public access for dashboards
|
||||
router.HandleFunc("/api/v1/public/dashboards/{id}", am.CheckWithoutClaims(
|
||||
ah.Signoz.Handlers.Dashboard.GetPublicData,
|
||||
authtypes.RelationRead, authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return ah.Signoz.Modules.Dashboard.GetPublicDashboardOrgAndSelectors(req.Context(), id, orgs)
|
||||
})).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/public/dashboards/{id}/widgets/{index}/query_range", am.CheckWithoutClaims(
|
||||
ah.Signoz.Handlers.Dashboard.GetPublicWidgetQueryRange,
|
||||
authtypes.RelationRead, authtypes.RelationRead,
|
||||
dashboardtypes.TypeableMetaResourcePublicDashboard,
|
||||
func(req *http.Request, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
id, err := valuer.NewUUID(mux.Vars(req)["id"])
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return ah.Signoz.Modules.Dashboard.GetPublicDashboardOrgAndSelectors(req.Context(), id, orgs)
|
||||
})).Methods(http.MethodGet)
|
||||
|
||||
// v3
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut)
|
||||
|
||||
@@ -192,7 +192,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
|
||||
r := baseapp.NewRouter()
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)
|
||||
|
||||
r.Use(otelmux.Middleware(
|
||||
"apiserver",
|
||||
|
||||
@@ -18,6 +18,8 @@ type AuthZ interface {
|
||||
// CheckWithTupleCreation takes upon the responsibility for generating the tuples alongside everything Check does.
|
||||
CheckWithTupleCreation(context.Context, authtypes.Claims, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error
|
||||
|
||||
CheckWithTupleCreationWithoutClaims(context.Context, valuer.UUID, authtypes.Relation, authtypes.Relation, authtypes.Typeable, []authtypes.Selector) error
|
||||
|
||||
// Batch Check returns error when the upstream authorization server is unavailable or for all the tuples of subject (s) doesn't have relation (r) on object (o).
|
||||
BatchCheck(context.Context, []*openfgav1.TupleKey) error
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package openfgaauthz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
authz "github.com/SigNoz/signoz/pkg/authz"
|
||||
@@ -121,13 +122,15 @@ func (provider *provider) Check(ctx context.Context, tupleReq *openfgav1.TupleKe
|
||||
func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.TupleKey) error {
|
||||
storeID, modelID := provider.getStoreIDandModelID()
|
||||
batchCheckItems := make([]*openfgav1.BatchCheckItem, 0)
|
||||
for _, tuple := range tupleReq {
|
||||
for idx, tuple := range tupleReq {
|
||||
batchCheckItems = append(batchCheckItems, &openfgav1.BatchCheckItem{
|
||||
TupleKey: &openfgav1.CheckRequestTupleKey{
|
||||
User: tuple.User,
|
||||
Relation: tuple.Relation,
|
||||
Object: tuple.Object,
|
||||
},
|
||||
// the batch check response is map[string] keyed by correlationID.
|
||||
CorrelationId: strconv.Itoa(idx),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,7 +156,26 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq []*openfgav1.
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeUser, claims.UserID, authtypes.Relation{})
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := authtypes.TypeableOrganization.Tuples(subject, translation, []authtypes.Selector{authtypes.MustNewSelector(authtypes.TypeOrganization, orgID.StringValue())}, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, _ authtypes.Relation, translation authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -186,7 +208,8 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
|
||||
return nil
|
||||
}
|
||||
return &openfgav1.WriteRequestWrites{
|
||||
TupleKeys: additions,
|
||||
TupleKeys: additions,
|
||||
OnDuplicate: "ignore",
|
||||
}
|
||||
}(),
|
||||
Deletes: func() *openfgav1.WriteRequestDeletes {
|
||||
@@ -195,6 +218,7 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
|
||||
}
|
||||
return &openfgav1.WriteRequestDeletes{
|
||||
TupleKeys: deletionTuplesWithoutCondition,
|
||||
OnMissing: "ignore",
|
||||
}
|
||||
}(),
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -17,15 +18,16 @@ const (
|
||||
|
||||
type AuthZ struct {
|
||||
logger *slog.Logger
|
||||
orgGetter organization.Getter
|
||||
authzService authz.AuthZ
|
||||
}
|
||||
|
||||
func NewAuthZ(logger *slog.Logger) *AuthZ {
|
||||
func NewAuthZ(logger *slog.Logger, orgGetter organization.Getter, authzService authz.AuthZ) *AuthZ {
|
||||
if logger == nil {
|
||||
panic("cannot build authz middleware, logger is empty")
|
||||
}
|
||||
|
||||
return &AuthZ{logger: logger}
|
||||
return &AuthZ{logger: logger, orgGetter: orgGetter, authzService: authzService}
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
@@ -107,7 +109,7 @@ func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackFn) http.HandlerFunc {
|
||||
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithClaimsFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
@@ -121,7 +123,7 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
return
|
||||
}
|
||||
|
||||
selectors, err := cb(req.Context(), claims)
|
||||
selectors, err := cb(req, claims)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -136,3 +138,28 @@ func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relatio
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (middleware *AuthZ) CheckWithoutClaims(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackWithoutClaimsFn) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
orgs, err := middleware.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
selectors, orgID, err := cb(req, orgs)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = middleware.authzService.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, translation, typeable, selectors)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
next(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,11 +7,30 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// enables public sharing for dashboard.
|
||||
CreatePublic(context.Context, valuer.UUID, *dashboardtypes.PublicDashboard) error
|
||||
|
||||
// gets the config for public sharing by org_id and dashboard_id.
|
||||
GetPublic(context.Context, valuer.UUID, valuer.UUID) (*dashboardtypes.PublicDashboard, error)
|
||||
|
||||
// get the dashboard data by public dashboard id
|
||||
GetDashboardByPublicID(context.Context, valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
// gets the org for the given public dashboard
|
||||
GetPublicDashboardOrgAndSelectors(ctx context.Context, id valuer.UUID, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error)
|
||||
|
||||
// updates the config for public sharing.
|
||||
UpdatePublic(context.Context, *dashboardtypes.PublicDashboard) error
|
||||
|
||||
// disables the public sharing for the dashboard.
|
||||
DeletePublic(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
@@ -32,6 +51,18 @@ type Module interface {
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
CreatePublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicData(http.ResponseWriter, *http.Request)
|
||||
|
||||
GetPublicWidgetQueryRange(http.ResponseWriter, *http.Request)
|
||||
|
||||
UpdatePublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
DeletePublic(http.ResponseWriter, *http.Request)
|
||||
|
||||
Create(http.ResponseWriter, *http.Request)
|
||||
|
||||
Update(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -4,12 +4,16 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/http/binding"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/transition"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
@@ -21,10 +25,12 @@ import (
|
||||
type handler struct {
|
||||
module dashboard.Module
|
||||
providerSettings factory.ProviderSettings
|
||||
querier querier.Querier
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewHandler(module dashboard.Module, providerSettings factory.ProviderSettings) dashboard.Handler {
|
||||
return &handler{module: module, providerSettings: providerSettings}
|
||||
func NewHandler(module dashboard.Module, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing) dashboard.Handler {
|
||||
return &handler{module: module, providerSettings: providerSettings, querier: querier, licensing: licensing}
|
||||
}
|
||||
|
||||
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -196,3 +202,278 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) CreatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.licensing.GetActive(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(dashboardtypes.PostablePublicDashboard)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard := dashboardtypes.NewPublicDashboard(req.TimeRangeEnabled, req.DefaultTimeRange, id)
|
||||
err = handler.module.CreatePublic(ctx, valuer.MustNewUUID(claims.OrgID), publicDashboard)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) GetPublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.licensing.GetActive(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, dashboardtypes.NewGettablePublicDashboard(publicDashboard))
|
||||
}
|
||||
|
||||
func (handler *handler) GetPublicData(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, dashboard.OrgID, valuer.MustNewUUID(dashboard.ID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettablePublicDashboardData, err := dashboardtypes.NewPublicDashboardDataFromDashboard(dashboard, publicDashboard)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, gettablePublicDashboardData)
|
||||
}
|
||||
|
||||
func (handler *handler) GetPublicWidgetQueryRange(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
widgetIndex, ok := mux.Vars(r)["index"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "widget index is missing from the path"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.GetDashboardByPublicID(ctx, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, dashboard.OrgID, valuer.MustNewUUID(dashboard.ID))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
widgetIdxInt, err := strconv.ParseInt(widgetIndex, 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid widget index"))
|
||||
return
|
||||
}
|
||||
|
||||
var startTime, endTime uint64
|
||||
if publicDashboard.TimeRangeEnabled {
|
||||
startTimeUint, err := strconv.ParseUint(r.URL.Query().Get("startTime"), 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid startTime"))
|
||||
return
|
||||
}
|
||||
|
||||
endTimeUint, err := strconv.ParseUint(r.URL.Query().Get("endTime"), 10, 64)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, dashboardtypes.ErrCodePublicDashboardInvalidInput, "invalid endTime"))
|
||||
return
|
||||
}
|
||||
|
||||
startTime = startTimeUint
|
||||
endTime = endTimeUint
|
||||
} else {
|
||||
timeRange, err := time.ParseDuration(publicDashboard.DefaultTimeRange)
|
||||
if err != nil {
|
||||
// this should't happen as we shouldn't let such values in DB
|
||||
panic(err)
|
||||
}
|
||||
|
||||
startTime = uint64(time.Now().Add(-timeRange).UnixMilli())
|
||||
endTime = uint64(time.Now().UnixMilli())
|
||||
}
|
||||
|
||||
query, err := dashboard.GetWidgetQuery(startTime, endTime, widgetIdxInt, handler.providerSettings.Logger)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
queryRangeResults, err := handler.querier.QueryRange(ctx, dashboard.OrgID, query)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, queryRangeResults)
|
||||
}
|
||||
|
||||
func (handler *handler) UpdatePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.licensing.GetActive(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(dashboardtypes.UpdatablePublicDashboard)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard, err := handler.module.GetPublic(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
publicDashboard.Update(req.TimeRangeEnabled, req.DefaultTimeRange)
|
||||
err = handler.module.UpdatePublic(ctx, publicDashboard)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) DeletePublic(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.licensing.GetActive(ctx, valuer.MustNewUUID(claims.OrgID))
|
||||
if err != nil {
|
||||
render.Error(rw, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.DeletePublic(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -19,14 +22,18 @@ type module struct {
|
||||
store dashboardtypes.Store
|
||||
settings factory.ScopedProviderSettings
|
||||
analytics analytics.Analytics
|
||||
orgGetter organization.Getter
|
||||
role role.Module
|
||||
}
|
||||
|
||||
func NewModule(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics) dashboard.Module {
|
||||
func NewModule(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, role role.Module) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/impldashboard")
|
||||
return &module{
|
||||
store: NewStore(sqlstore),
|
||||
settings: scopedProviderSettings,
|
||||
analytics: analytics,
|
||||
orgGetter: orgGetter,
|
||||
role: role,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,17 +57,79 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy s
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publicDashboard *dashboardtypes.PublicDashboard) error {
|
||||
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.role.Assign(ctx, role.ID, orgID, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
additionObject := authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
Type: authtypes.TypeMetaResource,
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.CreatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
storableDashboard, err := module.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboard, err := dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard)
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetPublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) (*dashboardtypes.PublicDashboard, error) {
|
||||
storablePublicDashboard, err := module.store.GetPublic(ctx, dashboardID.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dashboard, nil
|
||||
|
||||
return dashboardtypes.NewPublicDashboardFromStorablePublicDashboard(storablePublicDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByPublicID(ctx context.Context, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
storableDashboard, err := module.store.GetDashboardByPublicID(ctx, id.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
|
||||
}
|
||||
|
||||
func (module *module) GetPublicDashboardOrgAndSelectors(ctx context.Context, id valuer.UUID, orgs []*types.Organization) ([]authtypes.Selector, valuer.UUID, error) {
|
||||
orgIDs := make([]string, len(orgs))
|
||||
for idx, org := range orgs {
|
||||
orgIDs[idx] = org.ID.StringValue()
|
||||
}
|
||||
|
||||
storableDashboard, err := module.store.GetDashboardByOrgsAndPublicID(ctx, orgIDs, id.StringValue())
|
||||
if err != nil {
|
||||
return nil, valuer.UUID{}, err
|
||||
}
|
||||
|
||||
return []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, id.StringValue()),
|
||||
}, storableDashboard.OrgID, nil
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
@@ -69,12 +138,7 @@ func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboard
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboards, err := dashboardtypes.NewDashboardsFromStorableDashboards(storableDashboards)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dashboards, nil
|
||||
return dashboardtypes.NewDashboardsFromStorableDashboards(storableDashboards), nil
|
||||
}
|
||||
|
||||
func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, updatableDashboard dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error) {
|
||||
@@ -101,6 +165,10 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (module *module) UpdatePublic(ctx context.Context, publicDashboard *dashboardtypes.PublicDashboard) error {
|
||||
return module.store.UpdatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
|
||||
}
|
||||
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
|
||||
dashboard, err := module.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
@@ -134,7 +202,61 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err := module.store.Delete(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.DeletePublic(ctx, id.StringValue())
|
||||
if err != nil {
|
||||
// do not do anything if no public config exists.
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
publicDashboard, err := module.GetPublic(ctx, orgID, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := module.role.GetOrCreate(ctx, roletypes.NewRole(roletypes.AnonymousUserRoleName, roletypes.AnonymousUserRoleDescription, roletypes.RoleTypeManaged.StringValue(), orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionObject := authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
Type: authtypes.TypeMetaResource,
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.role.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error) {
|
||||
@@ -225,5 +347,5 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{dashboardtypes.TypeableResourceDashboard, dashboardtypes.TypeableResourcesDashboards}
|
||||
return []authtypes.Typeable{dashboardtypes.TypeableMetaResourceDashboard, dashboardtypes.TypeableMetaResourcesDashboards}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
@@ -31,9 +32,22 @@ func (store *store) Create(ctx context.Context, storabledashboard *dashboardtype
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) CreatePublic(ctx context.Context, storable *dashboardtypes.StorablePublicDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(storable).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, dashboardtypes.ErrCodePublicDashboardAlreadyExists, "dashboard with id %s is already public", storable.DashboardID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.StorableDashboard, error) {
|
||||
storableDashboard := new(dashboardtypes.StorableDashboard)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
@@ -49,9 +63,61 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return storableDashboard, nil
|
||||
}
|
||||
|
||||
func (store *store) GetPublic(ctx context.Context, dashboardID string) (*dashboardtypes.StorablePublicDashboard, error) {
|
||||
storable := new(dashboardtypes.StorablePublicDashboard)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storable).
|
||||
Where("dashboard_id = ?", dashboardID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, dashboardtypes.ErrCodePublicDashboardNotFound, "dashboard with id %s isn't public", dashboardID)
|
||||
}
|
||||
|
||||
return storable, nil
|
||||
}
|
||||
|
||||
func (store *store) GetDashboardByOrgsAndPublicID(ctx context.Context, orgIDs []string, id string) (*dashboardtypes.StorableDashboard, error) {
|
||||
storable := new(dashboardtypes.StorableDashboard)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storable).
|
||||
Join("JOIN public_dashboard").
|
||||
JoinOn("public_dashboard.dashboard_id = dashboard.id").
|
||||
Where("public_dashboard.id = ?", id).
|
||||
Where("org_id IN (?)", bun.In(orgIDs)).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, dashboardtypes.ErrCodePublicDashboardNotFound, "couldn't find dashboard with id %s ", id)
|
||||
}
|
||||
|
||||
return storable, nil
|
||||
}
|
||||
|
||||
func (store *store) GetDashboardByPublicID(ctx context.Context, id string) (*dashboardtypes.StorableDashboard, error) {
|
||||
storable := new(dashboardtypes.StorableDashboard)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storable).
|
||||
Join("JOIN public_dashboard").
|
||||
JoinOn("public_dashboard.dashboard_id = dashboard.id").
|
||||
Where("public_dashboard.id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, dashboardtypes.ErrCodePublicDashboardNotFound, "couldn't find dashboard with id %s ", id)
|
||||
}
|
||||
|
||||
return storable, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.StorableDashboard, error) {
|
||||
storableDashboards := make([]*dashboardtypes.StorableDashboard, 0)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
@@ -60,7 +126,7 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardty
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "no dashboards found in orgID %s", orgID)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return storableDashboards, nil
|
||||
@@ -76,7 +142,22 @@ func (store *store) Update(ctx context.Context, orgID valuer.UUID, storableDashb
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeAlreadyExists, "dashboard with id %s doesn't exist", storableDashboard.ID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", storableDashboard.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) UpdatePublic(ctx context.Context, storable *dashboardtypes.StorablePublicDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(storable).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, dashboardtypes.ErrCodePublicDashboardNotFound, "dashboard with id %s isn't public", storable.DashboardID)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -97,3 +178,24 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) DeletePublic(ctx context.Context, dashboardID string) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewDelete().
|
||||
Model(new(dashboardtypes.StorablePublicDashboard)).
|
||||
Where("dashboard_id = ?", dashboardID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, dashboardtypes.ErrCodePublicDashboardNotFound, "dashboard with id %s isn't public", dashboardID)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
role, err := handler.module.Create(ctx, valuer.MustNewUUID(claims.OrgID), req.DisplayName, req.Description)
|
||||
err = handler.module.Create(ctx, roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom.StringValue(), valuer.MustNewUUID(claims.OrgID)))
|
||||
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) {
|
||||
@@ -150,12 +150,7 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -167,7 +162,14 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.Patch(ctx, valuer.MustNewUUID(claims.OrgID), roleID, req.DisplayName, req.Description)
|
||||
role, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
role.PatchMetadata(req.Name, req.Description)
|
||||
err = handler.module.Patch(ctx, valuer.MustNewUUID(claims.OrgID), role)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -184,23 +186,13 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
relationStr, ok := mux.Vars(r)["relation"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
|
||||
return
|
||||
}
|
||||
relation, err := authtypes.NewRelation(relationStr)
|
||||
relation, err := authtypes.NewRelation(mux.Vars(r)["relation"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -218,7 +210,7 @@ func (handler *handler) PatchObjects(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), roleID, relation, patchableObjects.Additions, patchableObjects.Deletions)
|
||||
err = handler.module.PatchObjects(ctx, valuer.MustNewUUID(claims.OrgID), id, relation, patchableObjects.Additions, patchableObjects.Deletions)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
@@ -235,18 +227,13 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
id, err := valuer.NewUUID(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), roleID)
|
||||
err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -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"
|
||||
@@ -25,15 +26,23 @@ func NewModule(store roletypes.Store, authz authz.AuthZ, registry []role.Registe
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, displayName, description string) (*roletypes.Role, error) {
|
||||
role := roletypes.NewRole(displayName, description, orgID)
|
||||
func (module *module) Create(ctx context.Context, role *roletypes.Role) error {
|
||||
return module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
storableRole, err := roletypes.NewStorableRoleFromRole(role)
|
||||
func (module *module) GetOrCreate(ctx context.Context, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
existingRole, err := module.store.GetByNameAndOrgID(ctx, role.Name, role.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, storableRole)
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = module.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,12 +72,7 @@ func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
return roletypes.NewRoleFromStorableRole(storableRole), nil
|
||||
}
|
||||
|
||||
func (module *module) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
@@ -84,7 +88,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.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
@@ -107,39 +111,14 @@ func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes
|
||||
|
||||
roles := make([]*roletypes.Role, len(storableRoles))
|
||||
for idx, storableRole := range storableRoles {
|
||||
role, err := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles[idx] = role
|
||||
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role.PatchMetadata(displayName, description)
|
||||
updatedRole, err := roletypes.NewStorableRoleFromRole(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.Update(ctx, orgID, updatedRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (module *module) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
return module.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (module *module) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
@@ -161,6 +140,21 @@ func (module *module) PatchObjects(ctx context.Context, orgID valuer.UUID, id va
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) Assign(ctx context.Context, id valuer.UUID, orgID valuer.UUID, subject string) error {
|
||||
tuples, err := authtypes.TypeableRole.Tuples(
|
||||
subject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, id.StringValue()),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return module.authz.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,23 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByNameAndOrgID(ctx context.Context, name string, orgID valuer.UUID) (*roletypes.StorableRole, error) {
|
||||
role := new(roletypes.StorableRole)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(role).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("name = ?", name).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with name: %s doesn't exist", name)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.StorableRole, error) {
|
||||
roles := make([]*roletypes.StorableRole, 0)
|
||||
err := store.
|
||||
|
||||
@@ -10,30 +10,36 @@ import (
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// Creates the role metadata
|
||||
Create(context.Context, valuer.UUID, string, string) (*roletypes.Role, error)
|
||||
// Creates the role.
|
||||
Create(context.Context, *roletypes.Role) error
|
||||
|
||||
// Gets the role metadata
|
||||
// Gets the role if it exists or creates one.
|
||||
GetOrCreate(context.Context, *roletypes.Role) (*roletypes.Role, error)
|
||||
|
||||
// Gets the role
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
|
||||
|
||||
// Gets the objects associated with the given role and relation
|
||||
// Gets the objects associated with the given role and relation.
|
||||
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
|
||||
|
||||
// Lists all the roles metadata for the organization
|
||||
// Lists all the roles for the organization.
|
||||
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
|
||||
|
||||
// Gets all the typeable resources registered from role registry
|
||||
// Gets all the typeable resources registered from role registry.
|
||||
GetResources(context.Context) []*authtypes.Resource
|
||||
|
||||
// Patches the roles metadata
|
||||
Patch(context.Context, valuer.UUID, valuer.UUID, *string, *string) error
|
||||
// Patches the role.
|
||||
Patch(context.Context, valuer.UUID, *roletypes.Role) error
|
||||
|
||||
// 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
|
||||
|
||||
// Deletes the role metadata and tuples in authorization server
|
||||
// Deletes the role and tuples in authorization server.
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
// Assigns role to the given subject.
|
||||
Assign(context.Context, valuer.UUID, valuer.UUID, string) error
|
||||
|
||||
RegisterTypeable
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
r.Use(middleware.NewComment().Wrap)
|
||||
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)
|
||||
|
||||
api.RegisterRoutes(r, am)
|
||||
api.RegisterLogsRoutes(r, am)
|
||||
|
||||
@@ -2,6 +2,7 @@ package signoz
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel/impltracefunnel"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
@@ -46,14 +48,14 @@ type Handlers struct {
|
||||
Services services.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings) Handlers {
|
||||
func NewHandlers(modules Modules, providerSettings factory.ProviderSettings, querier querier.Querier, licensing licensing.Licensing) Handlers {
|
||||
return Handlers{
|
||||
Organization: implorganization.NewHandler(modules.OrgGetter, modules.OrgSetter),
|
||||
Preference: implpreference.NewHandler(modules.Preference),
|
||||
User: impluser.NewHandler(modules.User, modules.UserGetter),
|
||||
SavedView: implsavedview.NewHandler(modules.SavedView),
|
||||
Apdex: implapdex.NewHandler(modules.Apdex),
|
||||
Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings),
|
||||
Dashboard: impldashboard.NewHandler(modules.Dashboard, providerSettings, querier, licensing),
|
||||
QuickFilter: implquickfilter.NewHandler(modules.QuickFilter),
|
||||
TraceFunnel: impltracefunnel.NewHandler(modules.TraceFunnel),
|
||||
RawDataExport: implrawdataexport.NewHandler(modules.RawDataExport),
|
||||
|
||||
@@ -35,9 +35,9 @@ 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, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil)
|
||||
|
||||
handlers := NewHandlers(modules, providerSettings)
|
||||
handlers := NewHandlers(modules, providerSettings, nil, nil)
|
||||
|
||||
reflectVal := reflect.ValueOf(handlers)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -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,7 @@ 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/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/services"
|
||||
@@ -69,6 +71,7 @@ func NewModules(
|
||||
querier querier.Querier,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
authNs map[authtypes.AuthNProvider]authn.AuthN,
|
||||
authz authz.AuthZ,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
@@ -81,7 +84,7 @@ func NewModules(
|
||||
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()),
|
||||
SavedView: implsavedview.NewModule(sqlstore),
|
||||
Apdex: implapdex.NewModule(sqlstore),
|
||||
Dashboard: impldashboard.NewModule(sqlstore, providerSettings, analytics),
|
||||
Dashboard: impldashboard.NewModule(sqlstore, providerSettings, analytics, orgGetter, implrole.NewModule(implrole.NewStore(sqlstore), authz, nil)),
|
||||
User: user,
|
||||
UserGetter: userGetter,
|
||||
QuickFilter: quickfilter,
|
||||
|
||||
@@ -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, nil)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -139,6 +139,8 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewAddRoutePolicyFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddAuthTokenFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddAuthzFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddPublicDashboardsFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddRoleFactory(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"
|
||||
@@ -51,6 +52,7 @@ type SigNoz struct {
|
||||
Sharder sharder.Sharder
|
||||
StatsReporter statsreporter.StatsReporter
|
||||
Tokenizer pkgtokenizer.Tokenizer
|
||||
Authz authz.AuthZ
|
||||
Modules Modules
|
||||
Handlers Handlers
|
||||
}
|
||||
@@ -69,6 +71,7 @@ func New(
|
||||
sqlstoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]],
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
|
||||
authzCallback func(context.Context, sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config],
|
||||
) (*SigNoz, error) {
|
||||
// Initialize instrumentation
|
||||
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
|
||||
@@ -243,6 +246,13 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize authz
|
||||
authzProviderFactory := authzCallback(ctx, sqlstore)
|
||||
authz, err := authzProviderFactory.New(ctx, providerSettings, authz.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize user getter
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
|
||||
@@ -300,10 +310,10 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, authNs)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, authNs, authz)
|
||||
|
||||
// Initialize all handlers for the modules
|
||||
handlers := NewHandlers(modules, providerSettings)
|
||||
handlers := NewHandlers(modules, providerSettings, querier, licensing)
|
||||
|
||||
// Create a list of all stats collectors
|
||||
statsCollectors := []statsreporter.StatsCollector{
|
||||
@@ -337,6 +347,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
|
||||
@@ -358,6 +369,7 @@ func New(
|
||||
Emailing: emailing,
|
||||
Sharder: sharder,
|
||||
Tokenizer: tokenizer,
|
||||
Authz: authz,
|
||||
Modules: modules,
|
||||
Handlers: handlers,
|
||||
}, nil
|
||||
|
||||
82
pkg/sqlmigration/052_add_public_dashboards.go
Normal file
82
pkg/sqlmigration/052_add_public_dashboards.go
Normal file
@@ -0,0 +1,82 @@
|
||||
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 addPublicDashboards struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddPublicDashboardsFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_public_dashboards"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return newAddPublicDashboards(ctx, ps, c, sqlstore, sqlschema)
|
||||
})
|
||||
}
|
||||
|
||||
func newAddPublicDashboards(_ context.Context, _ factory.ProviderSettings, _ Config, sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) (SQLMigration, error) {
|
||||
return &addPublicDashboards{sqlstore: sqlstore, sqlschema: sqlschema}, nil
|
||||
}
|
||||
|
||||
func (migration *addPublicDashboards) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addPublicDashboards) 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{}
|
||||
tableSQL := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "public_dashboard",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "time_range_enabled", DataType: sqlschema.DataTypeBoolean, Nullable: false},
|
||||
{Name: "default_time_range", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "dashboard_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"id"}},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{ReferencingColumnName: sqlschema.ColumnName("dashboard_id"), ReferencedTableName: sqlschema.TableName("dashboard"), ReferencedColumnName: sqlschema.ColumnName("id")},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQL...)
|
||||
|
||||
indexSQL := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{
|
||||
TableName: "public_dashboard",
|
||||
ColumnNames: []sqlschema.ColumnName{"dashboard_id"},
|
||||
})
|
||||
sqls = append(sqls, indexSQL...)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addPublicDashboards) Down(_ context.Context, _ *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
91
pkg/sqlmigration/53_add_role.go
Normal file
91
pkg/sqlmigration/53_add_role.go
Normal file
@@ -0,0 +1,91 @@
|
||||
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 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: "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{"name", "org_id"}})
|
||||
sqls = append(sqls, indexSQLs...)
|
||||
|
||||
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 *addRole) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (m *alertMigrateV5) Migrate(ctx context.Context, ruleData map[string]any) b
|
||||
m.logger.InfoContext(ctx, "migrated querymap")
|
||||
|
||||
// wrap it in the v5 envelope
|
||||
envelope := m.wrapInV5Envelope(name, queryMap, "builder_query")
|
||||
envelope := m.WrapInV5Envelope(name, queryMap, "builder_query")
|
||||
m.logger.InfoContext(ctx, "envelope after wrap", "envelope", envelope)
|
||||
compositeQuery["queries"] = append(compositeQuery["queries"].([]any), envelope)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ type migrateCommon struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (migration *migrateCommon) wrapInV5Envelope(name string, queryMap map[string]any, queryType string) map[string]any {
|
||||
func NewMigrateCommon(logger *slog.Logger) *migrateCommon {
|
||||
return &migrateCommon{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (migration *migrateCommon) WrapInV5Envelope(name string, queryMap map[string]any, queryType string) map[string]any {
|
||||
// Create a properly structured v5 query
|
||||
v5Query := map[string]any{
|
||||
"name": name,
|
||||
|
||||
@@ -46,7 +46,7 @@ func (m *savedViewMigrateV5) Migrate(ctx context.Context, data map[string]any) b
|
||||
m.logger.InfoContext(ctx, "migrated querymap")
|
||||
|
||||
// wrap it in the v5 envelope
|
||||
envelope := m.wrapInV5Envelope(name, queryMap, "builder_query")
|
||||
envelope := m.WrapInV5Envelope(name, queryMap, "builder_query")
|
||||
m.logger.InfoContext(ctx, "envelope after wrap", "envelope", envelope)
|
||||
data["queries"] = append(data["queries"].([]any), envelope)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
nameRegex = regexp.MustCompile("^[a-z]{1,35}$")
|
||||
nameRegex = regexp.MustCompile("^[a-z-]{1,50}$")
|
||||
|
||||
_ json.Marshaler = new(Name)
|
||||
_ json.Unmarshaler = new(Name)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,13 +22,15 @@ var (
|
||||
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}$`)
|
||||
typeAnonymousSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
typeOrganizationSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
typeMetaResourceSelectorRegex = regexp.MustCompile(`^[0-9a-f]{8}(?:\-[0-9a-f]{4}){3}-[0-9a-f]{12}$`)
|
||||
// metaresources selectors are used to select either all or none
|
||||
typeMetaResourcesSelectorRegex = regexp.MustCompile(`^\*$`)
|
||||
)
|
||||
|
||||
type SelectorCallbackFn func(context.Context, Claims) ([]Selector, error)
|
||||
type SelectorCallbackWithClaimsFn func(*http.Request, Claims) ([]Selector, error)
|
||||
type SelectorCallbackWithoutClaimsFn func(*http.Request, []*types.Organization) ([]Selector, valuer.UUID, error)
|
||||
|
||||
type Selector struct {
|
||||
val string
|
||||
@@ -83,6 +87,11 @@ func IsValidSelector(typed Type, selector string) error {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeRoleSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeAnonymous:
|
||||
if !typeAnonymousSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeAnonymousSelectorRegex.String())
|
||||
}
|
||||
return nil
|
||||
case TypeOrganization:
|
||||
if !typeOrganizationSelectorRegex.MatchString(selector) {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeAuthZInvalidSelector, "selector must conform to regex %s", typeOrganizationSelectorRegex.String())
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package authtypes
|
||||
|
||||
func NewSubject(subjectType Type, selector string, relation Relation) (string, error) {
|
||||
if relation.IsZero() {
|
||||
return subjectType.StringValue() + ":" + selector, nil
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
func NewSubject(subjectType Typeable, selector string, orgID valuer.UUID, relation *Relation) (string, error) {
|
||||
if relation == nil {
|
||||
return subjectType.Prefix(orgID) + "/" + selector, nil
|
||||
}
|
||||
|
||||
return subjectType.StringValue() + ":" + selector + "#" + relation.StringValue(), nil
|
||||
return subjectType.Prefix(orgID) + "/" + selector + "#" + relation.StringValue(), nil
|
||||
}
|
||||
|
||||
func MustNewSubject(subjectType Type, selector string, relation Relation) string {
|
||||
subject, err := NewSubject(subjectType, selector, relation)
|
||||
func MustNewSubject(subjectType Typeable, selector string, orgID valuer.UUID, relation *Relation) string {
|
||||
subject, err := NewSubject(subjectType, selector, orgID, relation)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,15 @@ func NewObject(resource Resource, selector Selector) (*Object, error) {
|
||||
return &Object{Resource: resource, Selector: selector}, nil
|
||||
}
|
||||
|
||||
func MustNewObject(resource Resource, selector Selector) *Object {
|
||||
object, err := NewObject(resource, selector)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func MustNewObjectFromString(input string) *Object {
|
||||
parts := strings.Split(input, "/")
|
||||
if len(parts) != 4 {
|
||||
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
|
||||
var (
|
||||
TypeUser = Type{valuer.NewString("user")}
|
||||
TypeAnonymous = Type{valuer.NewString("anonymous")}
|
||||
TypeRole = Type{valuer.NewString("role")}
|
||||
TypeOrganization = Type{valuer.NewString("organization")}
|
||||
TypeMetaResource = Type{valuer.NewString("metaresource")}
|
||||
@@ -24,6 +25,7 @@ var (
|
||||
|
||||
var (
|
||||
TypeableUser = &typeableUser{}
|
||||
TypeableAnonymous = &typeableAnonymous{}
|
||||
TypeableRole = &typeableRole{}
|
||||
TypeableOrganization = &typeableOrganization{}
|
||||
)
|
||||
|
||||
37
pkg/types/authtypes/typeable_anonymous.go
Normal file
37
pkg/types/authtypes/typeable_anonymous.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package authtypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
var _ Typeable = new(typeableAnonymous)
|
||||
|
||||
var (
|
||||
AnonymousUser = valuer.UUID{}
|
||||
)
|
||||
|
||||
type typeableAnonymous struct{}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Tuples(subject string, relation Relation, selector []Selector, orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, selector := range selector {
|
||||
object := typeableAnonymous.Prefix(orgID) + "/" + selector.String()
|
||||
tuples = append(tuples, &openfgav1.TupleKey{User: subject, Relation: relation.StringValue(), Object: object})
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Type() Type {
|
||||
return TypeAnonymous
|
||||
}
|
||||
|
||||
func (typeableAnonymous *typeableAnonymous) Name() Name {
|
||||
return MustNewName("anonymous")
|
||||
}
|
||||
|
||||
// example: anonymous:organization/0199c47d-f61b-7833-bc5f-c0730f12f046/anonymous
|
||||
func (typeableAnonymous *typeableAnonymous) Prefix(orgID valuer.UUID) string {
|
||||
return typeableAnonymous.Type().StringValue() + ":" + "organization" + "/" + orgID.StringValue() + "/" + typeableAnonymous.Name().String()
|
||||
}
|
||||
@@ -3,22 +3,33 @@ package dashboardtypes
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/transition"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourceDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("dashboard"))
|
||||
TypeableResourcesDashboards = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("dashboards"))
|
||||
TypeableMetaResourceDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("dashboard"))
|
||||
TypeableMetaResourcePublicDashboard = authtypes.MustNewTypeableMetaResource(authtypes.MustNewName("public-dashboard"))
|
||||
TypeableMetaResourcesDashboards = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("dashboards"))
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeDashboardInvalidInput = errors.MustNewCode("dashboard_invalid_input")
|
||||
ErrCodeDashboardNotFound = errors.MustNewCode("dashboard_not_found")
|
||||
ErrCodeDashboardInvalidData = errors.MustNewCode("dashboard_invalid_data")
|
||||
ErrCodeDashboardInvalidWidgetQuery = errors.MustNewCode("dashboard_invalid_widget_query")
|
||||
)
|
||||
|
||||
type StorableDashboard struct {
|
||||
bun.BaseModel `bun:"table:dashboard"`
|
||||
bun.BaseModel `bun:"table:dashboard,alias:dashboard"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
@@ -97,7 +108,7 @@ func NewDashboard(orgID valuer.UUID, createdBy string, storableDashboardData Sto
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) (*Dashboard, error) {
|
||||
func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) *Dashboard {
|
||||
return &Dashboard{
|
||||
ID: storableDashboard.ID.StringValue(),
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
@@ -111,20 +122,16 @@ func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) (*D
|
||||
OrgID: storableDashboard.OrgID,
|
||||
Data: storableDashboard.Data,
|
||||
Locked: storableDashboard.Locked,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewDashboardsFromStorableDashboards(storableDashboards []*StorableDashboard) ([]*Dashboard, error) {
|
||||
func NewDashboardsFromStorableDashboards(storableDashboards []*StorableDashboard) []*Dashboard {
|
||||
dashboards := make([]*Dashboard, len(storableDashboards))
|
||||
for idx, storableDashboard := range storableDashboards {
|
||||
dashboard, err := NewDashboardFromStorableDashboard(storableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboards[idx] = dashboard
|
||||
dashboards[idx] = NewDashboardFromStorableDashboard(storableDashboard)
|
||||
}
|
||||
|
||||
return dashboards, nil
|
||||
return dashboards
|
||||
}
|
||||
|
||||
func NewGettableDashboardsFromDashboards(dashboards []*Dashboard) ([]*GettableDashboard, error) {
|
||||
@@ -313,14 +320,129 @@ func (lockUnlockDashboard *LockUnlockDashboard) UnmarshalJSON(src []byte) error
|
||||
return nil
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
Create(context.Context, *StorableDashboard) error
|
||||
func (dashboard *Dashboard) GetWidgetQuery(startTime, endTime uint64, widgetIndex int64, logger *slog.Logger) (*querybuildertypesv5.QueryRangeRequest, error) {
|
||||
type dashboardData struct {
|
||||
Widgets []struct {
|
||||
PanelTypes string `json:"panelTypes"`
|
||||
Query struct {
|
||||
Builder struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
} `json:"builder"`
|
||||
ClickhouseSQL []map[string]any `json:"clickhouse_sql"`
|
||||
PromQL []map[string]any `json:"promql"`
|
||||
QueryType string `json:"queryType"`
|
||||
} `json:"query"`
|
||||
} `json:"widgets"`
|
||||
}
|
||||
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableDashboard, error)
|
||||
dataJSON, err := json.Marshal(dashboard.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
List(context.Context, valuer.UUID) ([]*StorableDashboard, error)
|
||||
var data dashboardData
|
||||
err = json.Unmarshal(dataJSON, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
Update(context.Context, valuer.UUID, *StorableDashboard) error
|
||||
if len(data.Widgets) < int(widgetIndex)+1 {
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardInvalidInput, "widget with index %v doesn't exist", widgetIndex)
|
||||
}
|
||||
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
compositeQueries := []any{}
|
||||
widgetData := data.Widgets[widgetIndex]
|
||||
switch widgetData.Query.QueryType {
|
||||
case "builder":
|
||||
migrate := transition.NewMigrateCommon(logger)
|
||||
for _, query := range widgetData.Query.Builder.QueryData {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_query"))
|
||||
}
|
||||
for _, query := range widgetData.Query.Builder.QueryFormulas {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_formula"))
|
||||
}
|
||||
for _, query := range widgetData.Query.Builder.QueryTraceOperator {
|
||||
queryName, ok := query["queryName"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "cannot type cast query name as string")
|
||||
}
|
||||
compositeQueries = append(compositeQueries, migrate.WrapInV5Envelope(queryName, query, "builder_trace_operator"))
|
||||
}
|
||||
case "clickhouse_sql":
|
||||
for _, query := range widgetData.Query.ClickhouseSQL {
|
||||
envelope := map[string]any{
|
||||
"type": "clickhouse_sql",
|
||||
"spec": map[string]any{
|
||||
"name": query["name"],
|
||||
"query": query["query"],
|
||||
"disabled": query["disabled"],
|
||||
"legend": query["legend"],
|
||||
},
|
||||
}
|
||||
compositeQueries = append(compositeQueries, envelope)
|
||||
}
|
||||
case "promql":
|
||||
for _, query := range widgetData.Query.PromQL {
|
||||
envelope := map[string]any{
|
||||
"type": "promql",
|
||||
"spec": map[string]any{
|
||||
"name": query["name"],
|
||||
"query": query["query"],
|
||||
"disabled": query["disabled"],
|
||||
"legend": query["legend"],
|
||||
},
|
||||
}
|
||||
compositeQueries = append(compositeQueries, envelope)
|
||||
}
|
||||
}
|
||||
|
||||
queryRangeReq := map[string]any{
|
||||
"schemaVersion": "v1",
|
||||
"start": startTime,
|
||||
"end": endTime,
|
||||
"requestType": dashboard.getQueryRequestTypeFromPanelType(widgetData.PanelTypes),
|
||||
"compositeQuery": map[string]any{
|
||||
"queries": compositeQueries,
|
||||
},
|
||||
}
|
||||
|
||||
req, err := json.Marshal(queryRangeReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "invalid query request")
|
||||
}
|
||||
|
||||
queryRangeRequest := new(querybuildertypesv5.QueryRangeRequest)
|
||||
err = json.Unmarshal(req, queryRangeRequest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "invalid query request")
|
||||
}
|
||||
|
||||
return queryRangeRequest, nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) getQueryRequestTypeFromPanelType(panelType string) querybuildertypesv5.RequestType {
|
||||
switch panelType {
|
||||
case "graph", "bar":
|
||||
return querybuildertypesv5.RequestTypeTimeSeries
|
||||
case "table", "pie", "value":
|
||||
return querybuildertypesv5.RequestTypeScalar
|
||||
case "trace":
|
||||
return querybuildertypesv5.RequestTypeTrace
|
||||
case "list":
|
||||
return querybuildertypesv5.RequestTypeRaw
|
||||
case "histogram":
|
||||
return querybuildertypesv5.RequestTypeDistribution
|
||||
}
|
||||
|
||||
return querybuildertypesv5.RequestTypeUnknown
|
||||
}
|
||||
|
||||
265
pkg/types/dashboardtypes/public_dashboard.go
Normal file
265
pkg/types/dashboardtypes/public_dashboard.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodePublicDashboardInvalidInput = errors.MustNewCode("public_dashboard_invalid_input")
|
||||
ErrCodePublicDashboardNotFound = errors.MustNewCode("public_dashboard_not_found")
|
||||
ErrCodePublicDashboardAlreadyExists = errors.MustNewCode("public_dashboard_already_exists")
|
||||
)
|
||||
|
||||
type StorablePublicDashboard struct {
|
||||
bun.BaseModel `bun:"table:public_dashboard"`
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
TimeRangeEnabled bool `bun:"time_range_enabled,type:boolean,notnull"`
|
||||
DefaultTimeRange string `bun:"default_time_range,type:text,notnull"`
|
||||
DashboardID string `bun:"dashboard_id,type:text,notnull"`
|
||||
}
|
||||
|
||||
type PublicDashboard struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
|
||||
TimeRangeEnabled bool `json:"timeRangeEnabled"`
|
||||
DefaultTimeRange string `json:"defaultTimeRange"`
|
||||
DashboardID valuer.UUID `json:"dashboardId"`
|
||||
}
|
||||
|
||||
type GettablePublicDasbhboard struct {
|
||||
TimeRangeEnabled bool `json:"timeRangeEnabled"`
|
||||
DefaultTimeRange string `json:"defaultTimeRange"`
|
||||
PublicPath string `json:"publicPath"`
|
||||
}
|
||||
|
||||
type PostablePublicDashboard struct {
|
||||
TimeRangeEnabled bool `json:"timeRangeEnabled"`
|
||||
DefaultTimeRange string `json:"defaultTimeRange"`
|
||||
}
|
||||
|
||||
type UpdatablePublicDashboard struct {
|
||||
TimeRangeEnabled bool `json:"timeRangeEnabled"`
|
||||
DefaultTimeRange string `json:"defaultTimeRange"`
|
||||
}
|
||||
|
||||
type GettablePublicDashboardData struct {
|
||||
Dashboard *Dashboard `json:"dashboard"`
|
||||
PublicDashboard *GettablePublicDasbhboard `json:"publicDashboard"`
|
||||
}
|
||||
|
||||
func NewPublicDashboard(timeRangeEnabled bool, defaultTimeRange string, dashboardID valuer.UUID) *PublicDashboard {
|
||||
return &PublicDashboard{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
TimeRangeEnabled: timeRangeEnabled,
|
||||
DefaultTimeRange: defaultTimeRange,
|
||||
DashboardID: dashboardID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewStorablePublicDashboardFromPublicDashboard(publicDashboard *PublicDashboard) *StorablePublicDashboard {
|
||||
return &StorablePublicDashboard{
|
||||
Identifiable: publicDashboard.Identifiable,
|
||||
TimeAuditable: publicDashboard.TimeAuditable,
|
||||
TimeRangeEnabled: publicDashboard.TimeRangeEnabled,
|
||||
DefaultTimeRange: publicDashboard.DefaultTimeRange,
|
||||
DashboardID: publicDashboard.DashboardID.StringValue(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPublicDashboardFromStorablePublicDashboard(storable *StorablePublicDashboard) *PublicDashboard {
|
||||
return &PublicDashboard{
|
||||
Identifiable: storable.Identifiable,
|
||||
TimeAuditable: storable.TimeAuditable,
|
||||
TimeRangeEnabled: storable.TimeRangeEnabled,
|
||||
DefaultTimeRange: storable.DefaultTimeRange,
|
||||
DashboardID: valuer.MustNewUUID(storable.DashboardID),
|
||||
}
|
||||
}
|
||||
|
||||
func NewGettablePublicDashboard(publicDashboard *PublicDashboard) *GettablePublicDasbhboard {
|
||||
return &GettablePublicDasbhboard{
|
||||
TimeRangeEnabled: publicDashboard.TimeRangeEnabled,
|
||||
DefaultTimeRange: publicDashboard.DefaultTimeRange,
|
||||
PublicPath: publicDashboard.PublicPath(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPublicDashboardDataFromDashboard(dashboard *Dashboard, publicDashboard *PublicDashboard) (*GettablePublicDashboardData, error) {
|
||||
type dashboardData struct {
|
||||
Widgets []struct {
|
||||
PanelTypes string `json:"panelTypes"`
|
||||
Query struct {
|
||||
Builder struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
} `json:"builder"`
|
||||
ClickhouseSQL []map[string]any `json:"clickhouse_sql"`
|
||||
PromQL []map[string]any `json:"promql"`
|
||||
QueryType string `json:"queryType"`
|
||||
} `json:"query"`
|
||||
} `json:"widgets"`
|
||||
}
|
||||
|
||||
dataJSON, err := json.Marshal(dashboard.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
var data dashboardData
|
||||
err = json.Unmarshal(dataJSON, &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, ErrCodeDashboardInvalidData, "invalid dashboard data")
|
||||
}
|
||||
|
||||
for idx, widget := range data.Widgets {
|
||||
switch widget.Query.QueryType {
|
||||
case "builder":
|
||||
widget.Query.ClickhouseSQL = []map[string]any{}
|
||||
widget.Query.PromQL = []map[string]any{}
|
||||
|
||||
updatedQueryData := []map[string]any{}
|
||||
for _, queryData := range widget.Query.Builder.QueryData {
|
||||
updatedQueryMap := map[string]any{}
|
||||
updatedQueryMap["aggregations"] = queryData["aggregations"]
|
||||
updatedQueryMap["legend"] = queryData["legend"]
|
||||
updatedQueryMap["queryName"] = queryData["queryName"]
|
||||
updatedQueryMap["expression"] = queryData["expression"]
|
||||
updatedQueryData = append(updatedQueryData, updatedQueryMap)
|
||||
}
|
||||
widget.Query.Builder.QueryData = updatedQueryData
|
||||
|
||||
updatedQueryFormulas := []map[string]any{}
|
||||
for _, queryFormula := range widget.Query.Builder.QueryFormulas {
|
||||
updatedQueryFormulaMap := map[string]any{}
|
||||
updatedQueryFormulaMap["legend"] = queryFormula["legend"]
|
||||
updatedQueryFormulaMap["queryName"] = queryFormula["queryName"]
|
||||
updatedQueryFormulaMap["expression"] = queryFormula["expression"]
|
||||
updatedQueryFormulas = append(updatedQueryFormulas, updatedQueryFormulaMap)
|
||||
}
|
||||
widget.Query.Builder.QueryFormulas = updatedQueryFormulas
|
||||
|
||||
updatedQueryTraceOperator := []map[string]any{}
|
||||
for _, queryTraceOperator := range widget.Query.Builder.QueryTraceOperator {
|
||||
updatedQueryTraceOperatorMap := map[string]any{}
|
||||
updatedQueryTraceOperatorMap["aggregations"] = queryTraceOperator["aggregations"]
|
||||
updatedQueryTraceOperatorMap["legend"] = queryTraceOperator["legend"]
|
||||
updatedQueryTraceOperatorMap["queryName"] = queryTraceOperator["queryName"]
|
||||
updatedQueryTraceOperatorMap["expression"] = queryTraceOperator["expression"]
|
||||
updatedQueryTraceOperator = append(updatedQueryTraceOperator, updatedQueryTraceOperatorMap)
|
||||
}
|
||||
widget.Query.Builder.QueryTraceOperator = updatedQueryTraceOperator
|
||||
|
||||
case "clickhouse_sql":
|
||||
widget.Query.Builder = struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
}{}
|
||||
widget.Query.PromQL = []map[string]any{}
|
||||
|
||||
updatedClickhouseSQLQuery := []map[string]any{}
|
||||
for _, clickhouseSQLQuery := range widget.Query.ClickhouseSQL {
|
||||
updatedClickhouseSQLQueryMap := make(map[string]any)
|
||||
updatedClickhouseSQLQueryMap["legend"] = clickhouseSQLQuery["legend"]
|
||||
updatedClickhouseSQLQueryMap["name"] = clickhouseSQLQuery["name"]
|
||||
updatedClickhouseSQLQuery = append(updatedClickhouseSQLQuery, updatedClickhouseSQLQueryMap)
|
||||
}
|
||||
widget.Query.ClickhouseSQL = updatedClickhouseSQLQuery
|
||||
case "promql":
|
||||
widget.Query.Builder = struct {
|
||||
QueryData []map[string]any `json:"queryData"`
|
||||
QueryFormulas []map[string]any `json:"queryFormulas"`
|
||||
QueryTraceOperator []map[string]any `json:"queryTraceOperator"`
|
||||
}{}
|
||||
widget.Query.ClickhouseSQL = []map[string]any{}
|
||||
|
||||
updatedPromQLQuery := []map[string]any{}
|
||||
for _, promQLQuery := range widget.Query.PromQL {
|
||||
updatedPromQLQueryMap := make(map[string]any)
|
||||
updatedPromQLQueryMap["legend"] = promQLQuery["legend"]
|
||||
updatedPromQLQueryMap["name"] = promQLQuery["name"]
|
||||
updatedPromQLQuery = append(updatedPromQLQuery, updatedPromQLQueryMap)
|
||||
}
|
||||
widget.Query.PromQL = updatedPromQLQuery
|
||||
default:
|
||||
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardInvalidWidgetQuery, "invalid query type: %s", widget.Query.QueryType)
|
||||
}
|
||||
|
||||
if widgets, ok := dashboard.Data["widgets"].([]any); ok {
|
||||
if widgetMap, ok := widgets[idx].(map[string]any); ok {
|
||||
widgetMap["query"] = widget.Query
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &GettablePublicDashboardData{
|
||||
Dashboard: &Dashboard{
|
||||
Data: dashboard.Data,
|
||||
},
|
||||
PublicDashboard: &GettablePublicDasbhboard{
|
||||
TimeRangeEnabled: publicDashboard.TimeRangeEnabled,
|
||||
DefaultTimeRange: publicDashboard.DefaultTimeRange,
|
||||
PublicPath: publicDashboard.PublicPath(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (typ *PublicDashboard) Update(timeRangeEnabled bool, defaultTimeRange string) {
|
||||
typ.TimeRangeEnabled = timeRangeEnabled
|
||||
typ.DefaultTimeRange = defaultTimeRange
|
||||
typ.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
func (typ *PublicDashboard) PublicPath() string {
|
||||
return "/public/dashboard/" + typ.ID.StringValue()
|
||||
}
|
||||
|
||||
func (typ *PostablePublicDashboard) UnmarshalJSON(data []byte) error {
|
||||
type alias PostablePublicDashboard
|
||||
var temp alias
|
||||
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := time.ParseDuration(temp.DefaultTimeRange)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInvalidInput, ErrCodePublicDashboardInvalidInput, "unable to parse defaultTimeRange %s", temp.DefaultTimeRange)
|
||||
}
|
||||
|
||||
*typ = PostablePublicDashboard(temp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (typ *UpdatablePublicDashboard) UnmarshalJSON(data []byte) error {
|
||||
type alias UpdatablePublicDashboard
|
||||
var temp alias
|
||||
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := time.ParseDuration(temp.DefaultTimeRange)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInvalidInput, ErrCodePublicDashboardInvalidInput, "unable to parse defaultTimeRange %s", temp.DefaultTimeRange)
|
||||
}
|
||||
|
||||
*typ = UpdatablePublicDashboard(temp)
|
||||
return nil
|
||||
}
|
||||
33
pkg/types/dashboardtypes/store.go
Normal file
33
pkg/types/dashboardtypes/store.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
Create(context.Context, *StorableDashboard) error
|
||||
|
||||
CreatePublic(context.Context, *StorablePublicDashboard) error
|
||||
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableDashboard, error)
|
||||
|
||||
GetPublic(context.Context, string) (*StorablePublicDashboard, error)
|
||||
|
||||
GetDashboardByOrgsAndPublicID(context.Context, []string, string) (*StorableDashboard, error)
|
||||
|
||||
GetDashboardByPublicID(context.Context, string) (*StorableDashboard, error)
|
||||
|
||||
List(context.Context, valuer.UUID) ([]*StorableDashboard, error)
|
||||
|
||||
Update(context.Context, valuer.UUID, *StorableDashboard) error
|
||||
|
||||
UpdatePublic(context.Context, *StorablePublicDashboard) error
|
||||
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
DeletePublic(context.Context, string) error
|
||||
|
||||
RunInTx(context.Context, func(context.Context) error) error
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package roletypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
@@ -21,6 +22,20 @@ var (
|
||||
ErrCodeRoleFailedTransactionsFromString = errors.MustNewCode("role_failed_transactions_from_string")
|
||||
)
|
||||
|
||||
var (
|
||||
RoleNameRegex = regexp.MustCompile("^[a-z-]{1,50}$")
|
||||
)
|
||||
|
||||
var (
|
||||
RoleTypeCustom = valuer.NewString("custom")
|
||||
RoleTypeManaged = valuer.NewString("managed")
|
||||
)
|
||||
|
||||
var (
|
||||
AnonymousUserRoleName = "signoz-anonymous"
|
||||
AnonymousUserRoleDescription = "Role assigned to anonymous users for access to public resources."
|
||||
)
|
||||
|
||||
var (
|
||||
TypeableResourcesRoles = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("roles"))
|
||||
)
|
||||
@@ -30,26 +45,28 @@ type StorableRole struct {
|
||||
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
DisplayName string `bun:"display_name,type:string"`
|
||||
Name string `bun:"name,type:string"`
|
||||
Description string `bun:"description,type:string"`
|
||||
Type string `bun:"type,type:string"`
|
||||
OrgID string `bun:"org_id,type:string"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
DisplayName string `json:"displayName"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
OrgID valuer.UUID `json:"org_id"`
|
||||
}
|
||||
|
||||
type PostableRole struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type PatchableRole struct {
|
||||
DisplayName *string `json:"displayName"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -58,32 +75,29 @@ type PatchableObjects struct {
|
||||
Deletions []*authtypes.Object `json:"deletions"`
|
||||
}
|
||||
|
||||
func NewStorableRoleFromRole(role *Role) (*StorableRole, error) {
|
||||
func NewStorableRoleFromRole(role *Role) *StorableRole {
|
||||
return &StorableRole{
|
||||
Identifiable: role.Identifiable,
|
||||
TimeAuditable: role.TimeAuditable,
|
||||
DisplayName: role.DisplayName,
|
||||
Name: role.Name,
|
||||
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
|
||||
}
|
||||
|
||||
func NewRoleFromStorableRole(storableRole *StorableRole) *Role {
|
||||
return &Role{
|
||||
Identifiable: storableRole.Identifiable,
|
||||
TimeAuditable: storableRole.TimeAuditable,
|
||||
DisplayName: storableRole.DisplayName,
|
||||
Name: storableRole.Name,
|
||||
Description: storableRole.Description,
|
||||
OrgID: orgID,
|
||||
}, nil
|
||||
Type: storableRole.Type,
|
||||
OrgID: valuer.MustNewUUID(storableRole.OrgID),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRole(displayName, description string, orgID valuer.UUID) *Role {
|
||||
func NewRole(name, description string, roleType string, orgID valuer.UUID) *Role {
|
||||
return &Role{
|
||||
Identifiable: types.Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
@@ -92,8 +106,9 @@ func NewRole(displayName, description string, orgID valuer.UUID) *Role {
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Name: name,
|
||||
Description: description,
|
||||
Type: roleType,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
@@ -118,9 +133,9 @@ func NewPatchableObjects(additions []*authtypes.Object, deletions []*authtypes.O
|
||||
return &PatchableObjects{Additions: additions, Deletions: deletions}, nil
|
||||
}
|
||||
|
||||
func (role *Role) PatchMetadata(displayName, description *string) {
|
||||
if displayName != nil {
|
||||
role.DisplayName = *displayName
|
||||
func (role *Role) PatchMetadata(name, description *string) {
|
||||
if name != nil {
|
||||
role.Name = *name
|
||||
}
|
||||
if description != nil {
|
||||
role.Description = *description
|
||||
@@ -130,7 +145,7 @@ func (role *Role) PatchMetadata(displayName, description *string) {
|
||||
|
||||
func (role *PostableRole) UnmarshalJSON(data []byte) error {
|
||||
type shadowPostableRole struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -139,11 +154,15 @@ func (role *PostableRole) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if shadowRole.DisplayName == "" {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "displayName is missing from the request")
|
||||
if shadowRole.Name == "" {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "name is missing from the request")
|
||||
}
|
||||
|
||||
role.DisplayName = shadowRole.DisplayName
|
||||
if match := RoleNameRegex.MatchString(shadowRole.Name); !match {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "name must conform to the regex: %s", RoleNameRegex.String())
|
||||
}
|
||||
|
||||
role.Name = shadowRole.Name
|
||||
role.Description = shadowRole.Description
|
||||
|
||||
return nil
|
||||
@@ -151,7 +170,7 @@ func (role *PostableRole) UnmarshalJSON(data []byte) error {
|
||||
|
||||
func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||
type shadowPatchableRole struct {
|
||||
DisplayName *string `json:"displayName"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -160,11 +179,17 @@ func (role *PatchableRole) UnmarshalJSON(data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
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")
|
||||
if shadowRole.Name == nil && shadowRole.Description == nil {
|
||||
return errors.New(errors.TypeInvalidInput, ErrCodeRoleEmptyPatch, "empty role patch request received, at least one of name or description must be present")
|
||||
}
|
||||
|
||||
role.DisplayName = shadowRole.DisplayName
|
||||
if shadowRole.Name != nil {
|
||||
if match := RoleNameRegex.MatchString(*shadowRole.Name); !match {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeRoleInvalidInput, "name must conform to the regex: %s", RoleNameRegex.String())
|
||||
}
|
||||
}
|
||||
|
||||
role.Name = shadowRole.Name
|
||||
role.Description = shadowRole.Description
|
||||
|
||||
return nil
|
||||
@@ -177,9 +202,10 @@ func GetAdditionTuples(id valuer.UUID, orgID valuer.UUID, relation authtypes.Rel
|
||||
typeable := authtypes.MustNewTypeableFromType(object.Resource.Type, object.Resource.Name)
|
||||
transactionTuples, err := typeable.Tuples(
|
||||
authtypes.MustNewSubject(
|
||||
authtypes.TypeRole,
|
||||
authtypes.TypeableRole,
|
||||
id.String(),
|
||||
authtypes.RelationAssignee,
|
||||
orgID,
|
||||
&authtypes.RelationAssignee,
|
||||
),
|
||||
relation,
|
||||
[]authtypes.Selector{object.Selector},
|
||||
@@ -202,9 +228,10 @@ func GetDeletionTuples(id valuer.UUID, orgID valuer.UUID, relation authtypes.Rel
|
||||
typeable := authtypes.MustNewTypeableFromType(object.Resource.Type, object.Resource.Name)
|
||||
transactionTuples, err := typeable.Tuples(
|
||||
authtypes.MustNewSubject(
|
||||
authtypes.TypeRole,
|
||||
authtypes.TypeableRole,
|
||||
id.String(),
|
||||
authtypes.RelationAssignee,
|
||||
orgID,
|
||||
&authtypes.RelationAssignee,
|
||||
),
|
||||
relation,
|
||||
[]authtypes.Selector{object.Selector},
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
type Store interface {
|
||||
Create(context.Context, *StorableRole) error
|
||||
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableRole, error)
|
||||
GetByNameAndOrgID(context.Context, string, valuer.UUID) (*StorableRole, error)
|
||||
List(context.Context, valuer.UUID) ([]*StorableRole, error)
|
||||
Update(context.Context, valuer.UUID, *StorableRole) error
|
||||
Delete(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
Reference in New Issue
Block a user