mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 04:00:27 +00:00
MG-370 - Add fine grained access control to reports (#403)
* add access control to rules engine Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix build Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * remove unused variable Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix report database Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix variable naming Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix entity type Signed-off-by: Arvindh <arvindh91@gmail.com> * update authorize method Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix generate report Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * revert env changes Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix linter Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix failing linter Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update generate permission Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * revert go mod file Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * revert go mod file Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> --------- Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> Signed-off-by: Arvindh <arvindh91@gmail.com> Co-authored-by: Arvindh <arvindh91@gmail.com>
This commit is contained in:
+136
-6
@@ -19,40 +19,57 @@ import (
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
httpapi "github.com/absmach/magistrala/reports/api"
|
||||
"github.com/absmach/magistrala/reports/middleware"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
repg "github.com/absmach/magistrala/reports/postgres"
|
||||
"github.com/absmach/supermq"
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
mgauthz "github.com/absmach/supermq/pkg/authz"
|
||||
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
|
||||
"github.com/absmach/supermq/pkg/callout"
|
||||
dconsumer "github.com/absmach/supermq/pkg/domains/events/consumer"
|
||||
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/policies/spicedb"
|
||||
pgclient "github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
spicedbdecoder "github.com/absmach/supermq/pkg/spicedb"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/authzed/authzed-go/v1"
|
||||
"github.com/authzed/grpcutil"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "reports"
|
||||
envPrefixDB = "MG_REPORTS_DB_"
|
||||
envPrefixHTTP = "MG_REPORTS_HTTP_"
|
||||
envPrefixCallout = "MG_REPORTS_CALLOUT_"
|
||||
envPrefixAuth = "SMQ_AUTH_GRPC_"
|
||||
defDB = "repo"
|
||||
defSvcHTTPPort = "9017"
|
||||
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
|
||||
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
|
||||
templatePath = "template/reports_default_template.html"
|
||||
reportEntity = "report"
|
||||
)
|
||||
|
||||
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
|
||||
@@ -67,10 +84,16 @@ type config struct {
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
ESConsumerName string `env:"MG_REPORTS_EVENT_CONSUMER" envDefault:"reports"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
DefaultTemplatePath string `env:"MG_REPORTS_DEFAULT_TEMPLATE" envDefault:""`
|
||||
ConverterURL string `env:"MG_PDF_CONVERTER_URL" envDefault:"http://localhost:4000/pdf"`
|
||||
SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"`
|
||||
SpicedbPort string `env:"SMQ_SPICEDB_PORT" envDefault:"50051"`
|
||||
SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"`
|
||||
SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"`
|
||||
PermissionsFile string `env:"SMQ_PERMISSIONS_FILE" envDefault:"permission.yaml"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -131,6 +154,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
callCfg := callout.Config{}
|
||||
if err := env.ParseWithOptions(&callCfg, env.Options{Prefix: envPrefixCallout}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to parse callout config : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
dbConfig := pgclient.Config{Name: defDB}
|
||||
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
@@ -139,7 +169,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
db, err := pgclient.Setup(dbConfig, *repg.Migration())
|
||||
migration, err := repg.Migration()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
db, err := pgclient.Setup(dbConfig, *migration)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -170,6 +208,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
callout, err := callout.New(callCfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create new callout: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
grpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
|
||||
@@ -211,6 +256,15 @@ func main() {
|
||||
defer authzClient.Close()
|
||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
|
||||
ddatabase := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
drepo := dpostgres.NewRepository(ddatabase)
|
||||
|
||||
if err := dconsumer.DomainsEventsSubscribe(ctx, drepo, cfg.ESURL, cfg.ESConsumerName, logger); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create domains event store : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
regrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(®rpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
|
||||
@@ -231,7 +285,7 @@ func main() {
|
||||
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
svc, err := newService(database, runInfo, authz, ec, logger, readersClient, template, cfg.ConverterURL)
|
||||
svc, err := newService(cfg, database, runInfo, authz, ec, logger, readersClient, template, callout, tracer)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
@@ -271,21 +325,97 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, converterURL string) (reports.Service, error) {
|
||||
func newService(cfg config, db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient, template reports.ReportTemplate, callout callout.Callout, tracer trace.Tracer) (reports.Service, error) {
|
||||
repo := repg.NewRepository(db)
|
||||
idp := uuid.New()
|
||||
|
||||
emailerClient, err := emailer.New(&ec)
|
||||
emailClient, err := emailer.New(&ec)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
csvc := reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailerClient, readersClient, template, converterURL)
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
|
||||
policyService, err := newSpiceDBPolicyServiceEvaluator(cfg, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Policy service successfully connected to SpiceDB gRPC server")
|
||||
|
||||
availableActions, builtInRoles, err := availableActionsAndBuiltInRoles(cfg.SpicedbSchemaFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get available actions and built-in roles: %w", err)
|
||||
}
|
||||
|
||||
csvc, err := reports.NewService(repo, runInfo, policyService, idp, ticker.NewTicker(time.Second*30), emailClient, readersClient, template, cfg.ConverterURL, availableActions, builtInRoles)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create reports service: %w", err)
|
||||
}
|
||||
|
||||
permConfig, err := permissions.ParsePermissionsFile(cfg.PermissionsFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse permissions file: %w", err)
|
||||
}
|
||||
|
||||
reportOps, reportRoleOps, err := permConfig.GetEntityPermissions(reportEntity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get report permissions: %w", err)
|
||||
}
|
||||
|
||||
entitiesOps, err := permissions.NewEntitiesOperations(
|
||||
permissions.EntitiesPermission{
|
||||
operations.EntityType: reportOps,
|
||||
},
|
||||
permissions.EntitiesOperationDetails[permissions.Operation]{
|
||||
operations.EntityType: operations.OperationDetails(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create entities operations: %w", err)
|
||||
}
|
||||
|
||||
roleOps, err := permissions.NewOperations(roles.Operations(), reportRoleOps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create role operations: %w", err)
|
||||
}
|
||||
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc, err = middleware.NewCallout(csvc, callout, entitiesOps, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc = middleware.LoggingMiddleware(csvc, logger)
|
||||
counter, latency := prometheus.MakeMetrics("reports", "api")
|
||||
csvc = middleware.NewMetricsMiddleware(counter, latency, csvc)
|
||||
csvc = middleware.NewTracingMiddleware(tracer, csvc)
|
||||
|
||||
return csvc, nil
|
||||
}
|
||||
|
||||
func newSpiceDBPolicyServiceEvaluator(cfg config, logger *slog.Logger) (policies.Service, error) {
|
||||
client, err := authzed.NewClientWithExperimentalAPIs(
|
||||
fmt.Sprintf("%s:%s", cfg.SpicedbHost, cfg.SpicedbPort),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpcutil.WithInsecureBearerToken(cfg.SpicedbPreSharedKey),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps := spicedb.NewPolicyService(client, logger)
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func availableActionsAndBuiltInRoles(spicedbSchemaFile string) ([]roles.Action, map[roles.BuiltInRoleName][]roles.Action, error) {
|
||||
availableActions, err := spicedbdecoder.GetActionsFromSchema(spicedbSchemaFile, reportEntity)
|
||||
if err != nil {
|
||||
return []roles.Action{}, map[roles.BuiltInRoleName][]roles.Action{}, err
|
||||
}
|
||||
|
||||
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
|
||||
reports.BuiltInRoleAdmin: availableActions,
|
||||
}
|
||||
|
||||
return availableActions, builtInRoles, err
|
||||
}
|
||||
|
||||
+1
-1
@@ -477,4 +477,4 @@ MG_RELEASE_TAG=latest
|
||||
SMQ_ALLOW_UNVERIFIED_USER=true
|
||||
|
||||
# Set to yes to accept the EULA for the UI services. To view the EULA visit: https://github.com/absmach/eula
|
||||
MG_UI_DOCKER_ACCEPT_EULA=no
|
||||
MG_UI_DOCKER_ACCEPT_EULA=yes
|
||||
|
||||
@@ -475,6 +475,7 @@ services:
|
||||
MG_REPORTS_DEFAULT_TEMPLATE: ${MG_REPORTS_DEFAULT_TEMPLATE}
|
||||
MG_PDF_CONVERTER_URL: ${MG_PDF_CONVERTER_URL}
|
||||
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
|
||||
SMQ_ES_URL: ${SMQ_ES_URL}
|
||||
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
|
||||
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
|
||||
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
|
||||
|
||||
@@ -43,7 +43,7 @@ report:
|
||||
operations:
|
||||
- add: report_create_permission
|
||||
- list: report_read_permission
|
||||
- generate: report_create_permission
|
||||
- generate: report_read_permission
|
||||
- view: read_permission
|
||||
- update: update_permission
|
||||
- update_schedule: update_permission
|
||||
|
||||
@@ -3,4 +3,7 @@
|
||||
|
||||
package policies
|
||||
|
||||
const RulesType = "rules"
|
||||
const (
|
||||
RulesType = "rules"
|
||||
ReportsType = "reports"
|
||||
)
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
package operations
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
)
|
||||
import "github.com/absmach/supermq/pkg/permissions"
|
||||
|
||||
const EntityType = "rule"
|
||||
|
||||
|
||||
+130
-1
@@ -330,7 +330,136 @@ func TestAddRule(t *testing.T) {
|
||||
Time: now,
|
||||
},
|
||||
},
|
||||
err: re.ErrGoroutinesNotAllowed,
|
||||
err: re.ErrGoroutinesNotAllowed,
|
||||
addPoliciesErr: nil,
|
||||
addRoleErr: nil,
|
||||
deleteErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "Add rule with failed to add roles and failed to delete policies",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
},
|
||||
res: re.Rule{
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
Status: re.EnabledStatus,
|
||||
CreatedBy: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
addRoleErr: svcerr.ErrCreateEntity,
|
||||
deletePolicies: svcerr.ErrRemoveEntity,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
},
|
||||
{
|
||||
desc: "Add rule with failed to add policies",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
},
|
||||
res: re.Rule{
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
Status: re.EnabledStatus,
|
||||
CreatedBy: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
addPoliciesErr: svcerr.ErrAuthorization,
|
||||
err: svcerr.ErrAddPolicies,
|
||||
},
|
||||
{
|
||||
desc: "Add rule with failed to add policies and failed rollback",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
},
|
||||
res: re.Rule{
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
Status: re.EnabledStatus,
|
||||
CreatedBy: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
addPoliciesErr: svcerr.ErrAuthorization,
|
||||
deleteErr: svcerr.ErrRemoveEntity,
|
||||
err: svcerr.ErrRollbackRepo,
|
||||
},
|
||||
{
|
||||
desc: "Add rule with failed to add roles",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
},
|
||||
res: re.Rule{
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
Status: re.EnabledStatus,
|
||||
CreatedBy: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
addRoleErr: svcerr.ErrCreateEntity,
|
||||
err: svcerr.ErrAddPolicies,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func viewReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.ViewReportConfig(ctx, session, req.ID)
|
||||
cfg, err := svc.ViewReportConfig(ctx, session, req.ID, req.withRoles)
|
||||
if err != nil {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
@@ -333,9 +333,8 @@ func TestViewReportConfigEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id, false).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
|
||||
@@ -52,7 +52,8 @@ func (req addReportConfigReq) validate() error {
|
||||
}
|
||||
|
||||
type viewReportConfigReq struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
withRoles bool
|
||||
}
|
||||
|
||||
func (req viewReportConfigReq) validate() error {
|
||||
|
||||
+72
-59
@@ -17,6 +17,7 @@ import (
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
roleManagerHttp "github.com/absmach/supermq/pkg/roles/rolemanager/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
@@ -38,6 +39,8 @@ func MakeHandler(svc reports.Service, authn smqauthn.AuthNMiddleware, mux *chi.M
|
||||
r.Use(authn.WithOptions(smqauthn.WithDomainCheck(true)).Middleware())
|
||||
r.Route("/{domainID}", func(r chi.Router) {
|
||||
r.Route("/reports", func(r chi.Router) {
|
||||
d := roleManagerHttp.NewDecoder("reportID")
|
||||
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
generateReportEndpoint(svc),
|
||||
decodeGenerateReportRequest,
|
||||
@@ -45,6 +48,8 @@ func MakeHandler(svc reports.Service, authn smqauthn.AuthNMiddleware, mux *chi.M
|
||||
opts...,
|
||||
), "generate_report").ServeHTTP)
|
||||
|
||||
r = roleManagerHttp.EntityAvailableActionsRouter(svc, d, r, opts)
|
||||
|
||||
r.Route("/configs", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
addReportConfigEndpoint(svc),
|
||||
@@ -53,34 +58,6 @@ func MakeHandler(svc reports.Service, authn smqauthn.AuthNMiddleware, mux *chi.M
|
||||
opts...,
|
||||
), "add_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportConfigEndpoint(svc),
|
||||
decodeViewReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "view_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportConfigEndpoint(svc),
|
||||
decodeUpdateReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}/schedule", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportScheduleEndpoint(svc),
|
||||
decodeUpdateReportScheduleRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_scheduler").ServeHTTP)
|
||||
|
||||
r.Delete("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportConfigEndpoint(svc),
|
||||
decodeDeleteReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
listReportsConfigEndpoint(svc),
|
||||
decodeListReportsConfigRequest,
|
||||
@@ -88,40 +65,72 @@ func MakeHandler(svc reports.Service, authn smqauthn.AuthNMiddleware, mux *chi.M
|
||||
opts...,
|
||||
), "list_reports_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/enable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
enableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "enable_report_config").ServeHTTP)
|
||||
r.Route("/{reportID}", func(r chi.Router) {
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportConfigEndpoint(svc),
|
||||
decodeViewReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "view_report_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/disable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
disableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "disable_report_config").ServeHTTP)
|
||||
r.Patch("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportConfigEndpoint(svc),
|
||||
decodeUpdateReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_config").ServeHTTP)
|
||||
|
||||
r.Put("/{reportID}/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportTemplateEndpoint(svc),
|
||||
decodeUpdateReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_template").ServeHTTP)
|
||||
r.Patch("/schedule", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportScheduleEndpoint(svc),
|
||||
decodeUpdateReportScheduleRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_scheduler").ServeHTTP)
|
||||
|
||||
r.Get("/{reportID}/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportTemplateEndpoint(svc),
|
||||
decodeGetReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "get_report_template").ServeHTTP)
|
||||
r.Delete("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportConfigEndpoint(svc),
|
||||
decodeDeleteReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_config").ServeHTTP)
|
||||
|
||||
r.Delete("/{reportID}/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportTemplateEndpoint(svc),
|
||||
decodeDeleteReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_template").ServeHTTP)
|
||||
r.Post("/enable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
enableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "enable_report_config").ServeHTTP)
|
||||
|
||||
r.Post("/disable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
disableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "disable_report_config").ServeHTTP)
|
||||
|
||||
r.Put("/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportTemplateEndpoint(svc),
|
||||
decodeUpdateReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_template").ServeHTTP)
|
||||
|
||||
r.Get("/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportTemplateEndpoint(svc),
|
||||
decodeGetReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "get_report_template").ServeHTTP)
|
||||
|
||||
r.Delete("/template", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportTemplateEndpoint(svc),
|
||||
decodeDeleteReportTemplateRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_template").ServeHTTP)
|
||||
|
||||
roleManagerHttp.EntityRoleMangerRouter(svc, d, r, opts)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -170,7 +179,11 @@ func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (any, erro
|
||||
|
||||
func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
id := chi.URLParam(r, reportIdKey)
|
||||
return viewReportConfigReq{ID: id}, nil
|
||||
withRoles, err := apiutil.ReadBoolQuery(r, api.RolesKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return viewReportConfigReq{ID: id, withRoles: withRoles}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (any, error) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports
|
||||
|
||||
import "github.com/absmach/supermq/pkg/roles"
|
||||
|
||||
const BuiltInRoleAdmin roles.BuiltInRoleName = "admin"
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
smqauthz "github.com/absmach/supermq/pkg/authz"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
rolemgr "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,36 +29,47 @@ var (
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc reports.Service
|
||||
authz smqauthz.Authorization
|
||||
svc reports.Service
|
||||
authz smqauthz.Authorization
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
rolemgr.RoleManagerAuthorizationMiddleware
|
||||
}
|
||||
|
||||
// AuthorizationMiddleware adds authorization to the reports service.
|
||||
func AuthorizationMiddleware(svc reports.Service, authz smqauthz.Authorization) (reports.Service, error) {
|
||||
func AuthorizationMiddleware(svc reports.Service, authz smqauthz.Authorization, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (reports.Service, error) {
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ram, err := rolemgr.NewAuthorization(operations.EntityType, svc, authz, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
entitiesOps: entitiesOps,
|
||||
RoleManagerAuthorizationMiddleware: ram,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpAddReportConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpAddReportConfig, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainCreateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpViewReportConfig, session); err != nil {
|
||||
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, operations.OpViewReportConfig, session, operations.EntityType, id); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewReportConfig(ctx, session, id)
|
||||
return am.svc.ViewReportConfig(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpUpdateReportConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpUpdateReportConfig, session, operations.EntityType, cfg.ID); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
@@ -64,15 +77,15 @@ func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpUpdateReportSchedule, session); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainDeleteConfigs, err)
|
||||
if err := am.authorize(ctx, operations.OpUpdateReportSchedule, session, operations.EntityType, cfg.ID); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, reports.OpRemoveReportConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpRemoveReportConfig, session, operations.EntityType, id); err != nil {
|
||||
return errors.Wrap(errDomainDeleteConfigs, err)
|
||||
}
|
||||
|
||||
@@ -80,7 +93,7 @@ func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
if err := am.authorize(ctx, reports.OpListReportsConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpListReportsConfig, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return reports.ReportConfigPage{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
@@ -88,7 +101,7 @@ func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, sessio
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpEnableReportConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpEnableReportConfig, session, operations.EntityType, id); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
@@ -96,7 +109,7 @@ func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, reports.OpDisableReportConfig, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpDisableReportConfig, session, operations.EntityType, id); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
@@ -104,7 +117,7 @@ func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, sess
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
if err := am.authorize(ctx, reports.OpGenerateReport, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpGenerateReport, session, policies.DomainType, session.DomainID); err != nil {
|
||||
return reports.ReportPage{}, errors.Wrap(errDomainGenerateReports, err)
|
||||
}
|
||||
|
||||
@@ -112,7 +125,7 @@ func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session a
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportTemplate(ctx context.Context, session authn.Session, cfg reports.ReportConfig) error {
|
||||
if err := am.authorize(ctx, reports.OpUpdateReportTemplate, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpUpdateReportTemplate, session, operations.EntityType, cfg.ID); err != nil {
|
||||
return errors.Wrap(errDomainUpdateTemplates, err)
|
||||
}
|
||||
|
||||
@@ -120,7 +133,7 @@ func (am *authorizationMiddleware) UpdateReportTemplate(ctx context.Context, ses
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
if err := am.authorize(ctx, reports.OpViewReportTemplate, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpViewReportTemplate, session, operations.EntityType, id); err != nil {
|
||||
return "", errors.Wrap(errDomainViewTemplates, err)
|
||||
}
|
||||
|
||||
@@ -128,7 +141,7 @@ func (am *authorizationMiddleware) ViewReportTemplate(ctx context.Context, sessi
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DeleteReportTemplate(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, reports.OpDeleteReportTemplate, session); err != nil {
|
||||
if err := am.authorize(ctx, operations.OpDeleteReportTemplate, session, operations.EntityType, id); err != nil {
|
||||
return errors.Wrap(errDomainRemoveTemplates, err)
|
||||
}
|
||||
|
||||
@@ -139,8 +152,8 @@ func (am *authorizationMiddleware) StartScheduler(ctx context.Context) error {
|
||||
return am.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session) error {
|
||||
perm, err := reports.GetPermission(op)
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions.Operation, session authn.Session, objType, obj string) error {
|
||||
perm, err := am.entitiesOps.GetPermission(operations.EntityType, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,19 +163,19 @@ func (am *authorizationMiddleware) authorize(ctx context.Context, op permissions
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: perm,
|
||||
Object: obj,
|
||||
ObjectType: objType,
|
||||
Permission: perm.String(),
|
||||
}
|
||||
|
||||
var pat *smqauthz.PATReq
|
||||
if session.PatID != "" {
|
||||
opName := reports.OperationName(op)
|
||||
opName := am.entitiesOps.OperationName(operations.EntityType, op)
|
||||
pat = &smqauthz.PATReq{
|
||||
UserID: session.UserID,
|
||||
PatID: session.PatID,
|
||||
EntityID: session.DomainID,
|
||||
EntityType: reports.EntityType,
|
||||
EntityType: operations.EntityType,
|
||||
Operation: opName,
|
||||
Domain: session.DomainID,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
mgPolicies "github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/callout"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var _ reports.Service = (*calloutMiddleware)(nil)
|
||||
|
||||
type calloutMiddleware struct {
|
||||
svc reports.Service
|
||||
callout callout.Callout
|
||||
entitiesOps permissions.EntitiesOperations[permissions.Operation]
|
||||
rolemw.RoleManagerCalloutMiddleware
|
||||
}
|
||||
|
||||
const entityType = "report"
|
||||
|
||||
func NewCallout(svc reports.Service, callout callout.Callout, entitiesOps permissions.EntitiesOperations[permissions.Operation], roleOps permissions.Operations[permissions.RoleOperation]) (reports.Service, error) {
|
||||
call, err := rolemw.NewCallout(mgPolicies.ReportsType, svc, callout, roleOps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := entitiesOps.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &calloutMiddleware{
|
||||
svc: svc,
|
||||
callout: callout,
|
||||
entitiesOps: entitiesOps,
|
||||
RoleManagerCalloutMiddleware: call,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entities": cfg,
|
||||
"count": 1,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpAddReportConfig, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpViewReportConfig, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.ViewReportConfig(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": cfg.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateReportConfig, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.UpdateReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": cfg.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateReportSchedule, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpRemoveReportConfig, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cm.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
params := map[string]any{
|
||||
"pagemeta": pm,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpListReportsConfig, params); err != nil {
|
||||
return reports.ReportConfigPage{}, err
|
||||
}
|
||||
|
||||
return cm.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpEnableReportConfig, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpDisableReportConfig, params); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return cm.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": config.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpGenerateReport, params); err != nil {
|
||||
return reports.ReportPage{}, err
|
||||
}
|
||||
|
||||
return cm.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) UpdateReportTemplate(ctx context.Context, session authn.Session, cfg reports.ReportConfig) error {
|
||||
params := map[string]any{
|
||||
"entity_id": cfg.ID,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpUpdateReportTemplate, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cm.svc.UpdateReportTemplate(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpViewReportTemplate, params); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cm.svc.ViewReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) DeleteReportTemplate(ctx context.Context, session authn.Session, id string) error {
|
||||
params := map[string]any{
|
||||
"entity_id": id,
|
||||
}
|
||||
|
||||
if err := cm.callOut(ctx, session, operations.OpDeleteReportTemplate, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cm.svc.DeleteReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) StartScheduler(ctx context.Context) error {
|
||||
return cm.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (cm *calloutMiddleware) callOut(ctx context.Context, session authn.Session, op permissions.Operation, pld map[string]any) error {
|
||||
var entityID string
|
||||
if id, ok := pld["entity_id"].(string); ok {
|
||||
entityID = id
|
||||
}
|
||||
|
||||
req := callout.Request{
|
||||
BaseRequest: callout.BaseRequest{
|
||||
Operation: cm.entitiesOps.OperationName(entityType, op),
|
||||
EntityType: entityType,
|
||||
EntityID: entityID,
|
||||
CallerID: session.UserID,
|
||||
CallerType: policies.UserType,
|
||||
DomainID: session.DomainID,
|
||||
Time: time.Now().UTC(),
|
||||
},
|
||||
Payload: pld,
|
||||
}
|
||||
|
||||
if err := cm.callout.Callout(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
)
|
||||
|
||||
var _ reports.Service = (*loggingMiddleware)(nil)
|
||||
@@ -17,10 +18,15 @@ var _ reports.Service = (*loggingMiddleware)(nil)
|
||||
type loggingMiddleware struct {
|
||||
logger *slog.Logger
|
||||
svc reports.Service
|
||||
rolemw.RoleManagerLoggingMiddleware
|
||||
}
|
||||
|
||||
func LoggingMiddleware(svc reports.Service, logger *slog.Logger) reports.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
return &loggingMiddleware{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
RoleManagerLoggingMiddleware: rolemw.NewLogging("reports", svc, logger),
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) StartScheduler(ctx context.Context) (err error) {
|
||||
@@ -71,7 +77,7 @@ func (lm *loggingMiddleware) AddReportConfig(ctx context.Context, session authn.
|
||||
return lm.svc.AddReportConfig(ctx, session, config)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
|
||||
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
@@ -88,7 +94,7 @@ func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn
|
||||
}
|
||||
lm.logger.Info("View report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ViewReportConfig(ctx, session, id)
|
||||
return lm.svc.ViewReportConfig(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, config reports.ReportConfig) (res reports.ReportConfig, err error) {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
type metricsMiddleware struct {
|
||||
counter metrics.Counter
|
||||
latency metrics.Histogram
|
||||
service reports.Service
|
||||
rolemw.RoleManagerMetricsMiddleware
|
||||
}
|
||||
|
||||
var _ reports.Service = (*metricsMiddleware)(nil)
|
||||
|
||||
func NewMetricsMiddleware(counter metrics.Counter, latency metrics.Histogram, service reports.Service) reports.Service {
|
||||
return &metricsMiddleware{
|
||||
counter: counter,
|
||||
latency: latency,
|
||||
service: service,
|
||||
RoleManagerMetricsMiddleware: rolemw.NewMetrics("reports", service, counter, latency),
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "add_report_config").Add(1)
|
||||
mm.latency.With("method", "add_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "view_report_config").Add(1)
|
||||
mm.latency.With("method", "view_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ViewReportConfig(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_report_config").Add(1)
|
||||
mm.latency.With("method", "update_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_report_schedule").Add(1)
|
||||
mm.latency.With("method", "update_report_schedule").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "remove_report_config").Add(1)
|
||||
mm.latency.With("method", "remove_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "list_reports_config").Add(1)
|
||||
mm.latency.With("method", "list_reports_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "enable_report_config").Add(1)
|
||||
mm.latency.With("method", "enable_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "disable_report_config").Add(1)
|
||||
mm.latency.With("method", "disable_report_config").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) UpdateReportTemplate(ctx context.Context, session authn.Session, cfg reports.ReportConfig) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "update_report_template").Add(1)
|
||||
mm.latency.With("method", "update_report_template").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.UpdateReportTemplate(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "view_report_template").Add(1)
|
||||
mm.latency.With("method", "view_report_template").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.ViewReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) DeleteReportTemplate(ctx context.Context, session authn.Session, id string) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "delete_report_template").Add(1)
|
||||
mm.latency.With("method", "delete_report_template").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.DeleteReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "generate_report").Add(1)
|
||||
mm.latency.With("method", "generate_report").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) StartScheduler(ctx context.Context) error {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "start_scheduler").Add(1)
|
||||
mm.latency.With("method", "start_scheduler").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.StartScheduler(ctx)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
rolemw "github.com/absmach/supermq/pkg/roles/rolemanager/middleware"
|
||||
smqTracing "github.com/absmach/supermq/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type tracingMiddleware struct {
|
||||
tracer trace.Tracer
|
||||
svc reports.Service
|
||||
rolemw.RoleManagerTracing
|
||||
}
|
||||
|
||||
var _ reports.Service = (*tracingMiddleware)(nil)
|
||||
|
||||
func NewTracingMiddleware(tracer trace.Tracer, svc reports.Service) reports.Service {
|
||||
return &tracingMiddleware{
|
||||
tracer: tracer,
|
||||
svc: svc,
|
||||
RoleManagerTracing: rolemw.NewTracing("reports", svc, tracer),
|
||||
}
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "add_report_config", trace.WithAttributes(
|
||||
attribute.String("name", cfg.Name),
|
||||
attribute.String("domain_id", cfg.DomainID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "view_report_config", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ViewReportConfig(ctx, session, id, withRoles)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_report_config", trace.WithAttributes(
|
||||
attribute.String("id", cfg.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_report_schedule", trace.WithAttributes(
|
||||
attribute.String("id", cfg.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "remove_report_config", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "list_reports_config", trace.WithAttributes(
|
||||
attribute.Int("offset", int(pm.Offset)),
|
||||
attribute.Int("limit", int(pm.Limit)),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "enable_report_config", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "disable_report_config", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) UpdateReportTemplate(ctx context.Context, session authn.Session, cfg reports.ReportConfig) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "update_report_template", trace.WithAttributes(
|
||||
attribute.String("id", cfg.ID),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.UpdateReportTemplate(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "view_report_template", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ViewReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) DeleteReportTemplate(ctx context.Context, session authn.Session, id string) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "delete_report_template", trace.WithAttributes(
|
||||
attribute.String("id", id),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.DeleteReportTemplate(ctx, session, id)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "generate_report", trace.WithAttributes(
|
||||
attribute.String("config_id", config.ID),
|
||||
attribute.String("action", string(action)),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) StartScheduler(ctx context.Context) error {
|
||||
ctx, span := smqTracing.StartSpan(ctx, tm.tracer, "start_scheduler")
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.StartScheduler(ctx)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+1500
-12
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/permissions"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
)
|
||||
|
||||
const EntityType = "reports"
|
||||
|
||||
const (
|
||||
OpAddReportConfig = iota
|
||||
OpViewReportConfig
|
||||
OpUpdateReportConfig
|
||||
OpUpdateReportSchedule
|
||||
OpRemoveReportConfig
|
||||
OpListReportsConfig
|
||||
OpEnableReportConfig
|
||||
OpDisableReportConfig
|
||||
OpGenerateReport
|
||||
OpUpdateReportTemplate
|
||||
OpViewReportTemplate
|
||||
OpDeleteReportTemplate
|
||||
)
|
||||
|
||||
const (
|
||||
OpAddReportConfigStr = "OpAddReportConfig"
|
||||
OpViewReportConfigStr = "OpViewReportConfig"
|
||||
OpUpdateReportConfigStr = "OpUpdateReportConfig"
|
||||
OpUpdateReportScheduleStr = "OpUpdateReportSchedule"
|
||||
OpRemoveReportConfigStr = "OpRemoveReportConfig"
|
||||
OpListReportsConfigStr = "OpListReportsConfig"
|
||||
OpEnableReportConfigStr = "OpEnableReportConfig"
|
||||
OpDisableReportConfigStr = "OpDisableReportConfig"
|
||||
OpGenerateReportStr = "OpGenerateReport"
|
||||
OpUpdateReportTemplateStr = "OpUpdateReportTemplate"
|
||||
OpViewReportTemplateStr = "OpViewReportTemplate"
|
||||
OpDeleteReportTemplateStr = "OpDeleteReportTemplate"
|
||||
)
|
||||
|
||||
func GetPermission(op permissions.Operation) (string, error) {
|
||||
if op < OpAddReportConfig || op > OpDeleteReportTemplate {
|
||||
return "", errors.New("invalid operation")
|
||||
}
|
||||
return policies.MembershipPermission, nil
|
||||
}
|
||||
|
||||
func OperationName(op permissions.Operation) string {
|
||||
switch op {
|
||||
case OpAddReportConfig:
|
||||
return OpAddReportConfigStr
|
||||
case OpViewReportConfig:
|
||||
return OpViewReportConfigStr
|
||||
case OpUpdateReportConfig:
|
||||
return OpUpdateReportConfigStr
|
||||
case OpUpdateReportSchedule:
|
||||
return OpUpdateReportScheduleStr
|
||||
case OpRemoveReportConfig:
|
||||
return OpRemoveReportConfigStr
|
||||
case OpListReportsConfig:
|
||||
return OpListReportsConfigStr
|
||||
case OpEnableReportConfig:
|
||||
return OpEnableReportConfigStr
|
||||
case OpDisableReportConfig:
|
||||
return OpDisableReportConfigStr
|
||||
case OpGenerateReport:
|
||||
return OpGenerateReportStr
|
||||
case OpUpdateReportTemplate:
|
||||
return OpUpdateReportTemplateStr
|
||||
case OpViewReportTemplate:
|
||||
return OpViewReportTemplateStr
|
||||
case OpDeleteReportTemplate:
|
||||
return OpDeleteReportTemplateStr
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package operations
|
||||
|
||||
import "github.com/absmach/supermq/pkg/permissions"
|
||||
|
||||
const EntityType = "report"
|
||||
|
||||
// Report Operations.
|
||||
const (
|
||||
OpAddReportConfig permissions.Operation = iota
|
||||
OpViewReportConfig
|
||||
OpUpdateReportConfig
|
||||
OpUpdateReportSchedule
|
||||
OpRemoveReportConfig
|
||||
OpListReportsConfig
|
||||
OpEnableReportConfig
|
||||
OpDisableReportConfig
|
||||
OpGenerateReport
|
||||
OpUpdateReportTemplate
|
||||
OpViewReportTemplate
|
||||
OpDeleteReportTemplate
|
||||
)
|
||||
|
||||
func OperationDetails() map[permissions.Operation]permissions.OperationDetails {
|
||||
return map[permissions.Operation]permissions.OperationDetails{
|
||||
OpAddReportConfig: {
|
||||
Name: "add",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpViewReportConfig: {
|
||||
Name: "view",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateReportConfig: {
|
||||
Name: "update",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateReportSchedule: {
|
||||
Name: "update_schedule",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpRemoveReportConfig: {
|
||||
Name: "delete",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpListReportsConfig: {
|
||||
Name: "list",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpEnableReportConfig: {
|
||||
Name: "enable",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpDisableReportConfig: {
|
||||
Name: "disable",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpGenerateReport: {
|
||||
Name: "generate",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpUpdateReportTemplate: {
|
||||
Name: "update_template",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpViewReportTemplate: {
|
||||
Name: "view_template",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
OpDeleteReportTemplate: {
|
||||
Name: "delete_template",
|
||||
PermissionRequired: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,20 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
dpostgres "github.com/absmach/supermq/domains/postgres"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
func Migration() *migrate.MemoryMigrationSource {
|
||||
return &migrate.MemoryMigrationSource{
|
||||
func Migration() (*migrate.MemoryMigrationSource, error) {
|
||||
rolesMigration, err := rolesPostgres.Migration(rolesTableNamePrefix, entityTableName, entityIDColumnName)
|
||||
if err != nil {
|
||||
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
|
||||
}
|
||||
reportsMigration := &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "reports_01",
|
||||
@@ -48,4 +56,14 @@ func Migration() *migrate.MemoryMigrationSource {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
reportsMigration.Migrations = append(reportsMigration.Migrations, rolesMigration.Migrations...)
|
||||
|
||||
domainsMigration, err := dpostgres.Migration()
|
||||
if err != nil {
|
||||
return &migrate.MemoryMigrationSource{}, errors.Wrap(repoerr.ErrRoleMigration, err)
|
||||
}
|
||||
reportsMigration.Migrations = append(reportsMigration.Migrations, domainsMigration.Migrations...)
|
||||
|
||||
return reportsMigration, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
)
|
||||
|
||||
// dbReport represents the database structure for a Report.
|
||||
@@ -32,6 +33,8 @@ type dbReport struct {
|
||||
Metrics []byte `db:"metrics"`
|
||||
Email []byte `db:"email"`
|
||||
ReportTemplate reports.ReportTemplate `db:"report_template"`
|
||||
MemberID string `db:"member_id,omitempty"`
|
||||
Roles json.RawMessage `db:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func reportToDb(r reports.ReportConfig) (dbReport, error) {
|
||||
@@ -113,6 +116,13 @@ func dbToReport(dto dbReport) (reports.ReportConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var roles []roles.MemberRoleActions
|
||||
if dto.Roles != nil {
|
||||
if err := json.Unmarshal(dto.Roles, &roles); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
rpt := reports.ReportConfig{
|
||||
ID: dto.ID,
|
||||
Name: dto.Name,
|
||||
@@ -133,6 +143,7 @@ func dbToReport(dto dbReport) (reports.ReportConfig, error) {
|
||||
UpdatedAt: dto.UpdatedAt,
|
||||
UpdatedBy: dto.UpdatedBy,
|
||||
ReportTemplate: dto.ReportTemplate,
|
||||
Roles: roles,
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
|
||||
@@ -10,25 +10,36 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mgPolicies "github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
"github.com/absmach/supermq/pkg/postgres"
|
||||
rolesPostgres "github.com/absmach/supermq/pkg/roles/repo/postgres"
|
||||
)
|
||||
|
||||
const (
|
||||
rolesTableNamePrefix = "reports"
|
||||
entityTableName = "report_config"
|
||||
entityIDColumnName = "id"
|
||||
)
|
||||
|
||||
type PostgresRepository struct {
|
||||
DB postgres.Database
|
||||
eh errors.Handler
|
||||
rolesPostgres.Repository
|
||||
}
|
||||
|
||||
func NewRepository(db postgres.Database) reports.Repository {
|
||||
rolesRepo := rolesPostgres.NewRepository(db, mgPolicies.ReportsType, rolesTableNamePrefix, entityTableName, entityIDColumnName)
|
||||
errHandlerOptions := []errors.HandlerOption{
|
||||
postgres.WithDuplicateErrors(NewDuplicateErrors()),
|
||||
}
|
||||
return &PostgresRepository{
|
||||
DB: db,
|
||||
eh: postgres.NewErrorHandler(errHandlerOptions...),
|
||||
DB: db,
|
||||
eh: postgres.NewErrorHandler(errHandlerOptions...),
|
||||
Repository: rolesRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +103,155 @@ func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string)
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) RetrieveByIDWithRoles(ctx context.Context, id, memberID string) (reports.ReportConfig, error) {
|
||||
query := `
|
||||
WITH selected_report AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.domain_id
|
||||
FROM
|
||||
report_config r
|
||||
WHERE
|
||||
r.id = :id
|
||||
LIMIT 1
|
||||
),
|
||||
selected_report_roles AS (
|
||||
SELECT
|
||||
rr.entity_id AS report_id,
|
||||
rrm.member_id AS member_id,
|
||||
rr.id AS role_id,
|
||||
rr."name" AS role_name,
|
||||
jsonb_agg(DISTINCT rra."action") AS actions,
|
||||
'direct' AS access_type,
|
||||
'' AS access_provider_id
|
||||
FROM
|
||||
reports_roles rr
|
||||
JOIN
|
||||
reports_role_members rrm ON rr.id = rrm.role_id
|
||||
JOIN
|
||||
reports_role_actions rra ON rr.id = rra.role_id
|
||||
JOIN
|
||||
selected_report sr ON sr.id = rr.entity_id
|
||||
AND rrm.member_id = :member_id
|
||||
GROUP BY
|
||||
rr.entity_id, rr.id, rr.name, rrm.member_id
|
||||
),
|
||||
selected_domain_roles AS (
|
||||
SELECT
|
||||
sr.id AS report_id,
|
||||
drm.member_id AS member_id,
|
||||
dr.id AS role_id,
|
||||
dr."name" AS role_name,
|
||||
jsonb_agg(DISTINCT all_actions."action") AS actions,
|
||||
'domain' AS access_type,
|
||||
dr.entity_id AS access_provider_id
|
||||
FROM
|
||||
domains d
|
||||
JOIN
|
||||
selected_report sr ON sr.domain_id = d.id
|
||||
JOIN
|
||||
domains_roles dr ON dr.entity_id = d.id
|
||||
JOIN
|
||||
domains_role_members drm ON dr.id = drm.role_id
|
||||
JOIN
|
||||
domains_role_actions dra ON dr.id = dra.role_id
|
||||
JOIN
|
||||
domains_role_actions all_actions ON dr.id = all_actions.role_id
|
||||
WHERE
|
||||
drm.member_id = :member_id
|
||||
AND dra."action" LIKE 'report%'
|
||||
GROUP BY
|
||||
sr.id, dr.entity_id, dr.id, dr."name", drm.member_id
|
||||
),
|
||||
all_roles AS (
|
||||
SELECT
|
||||
srr.report_id,
|
||||
srr.member_id,
|
||||
srr.role_id,
|
||||
srr.role_name,
|
||||
srr.actions,
|
||||
srr.access_type,
|
||||
srr.access_provider_id
|
||||
FROM
|
||||
selected_report_roles srr
|
||||
UNION
|
||||
SELECT
|
||||
sdr.report_id,
|
||||
sdr.member_id,
|
||||
sdr.role_id,
|
||||
sdr.role_name,
|
||||
sdr.actions,
|
||||
sdr.access_type,
|
||||
sdr.access_provider_id
|
||||
FROM
|
||||
selected_domain_roles sdr
|
||||
),
|
||||
final_roles AS (
|
||||
SELECT
|
||||
ar.report_id,
|
||||
ar.member_id,
|
||||
jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'role_id', ar.role_id,
|
||||
'role_name', ar.role_name,
|
||||
'actions', ar.actions,
|
||||
'access_type', ar.access_type,
|
||||
'access_provider_id', ar.access_provider_id
|
||||
)
|
||||
) AS roles
|
||||
FROM all_roles ar
|
||||
GROUP BY
|
||||
ar.report_id, ar.member_id
|
||||
)
|
||||
SELECT
|
||||
r2.id,
|
||||
r2."name",
|
||||
r2.description,
|
||||
r2.domain_id,
|
||||
r2.status,
|
||||
r2.created_at,
|
||||
r2.created_by,
|
||||
r2.updated_at,
|
||||
r2.updated_by,
|
||||
r2.due,
|
||||
r2.recurring,
|
||||
r2.recurring_period,
|
||||
r2.start_datetime,
|
||||
r2.config,
|
||||
r2.email,
|
||||
r2.metrics,
|
||||
r2.report_template,
|
||||
fr.member_id,
|
||||
fr.roles
|
||||
FROM report_config r2
|
||||
JOIN final_roles fr ON fr.report_id = r2.id
|
||||
`
|
||||
parameters := map[string]any{
|
||||
"id": id,
|
||||
"member_id": memberID,
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, query, parameters)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
dbreport := dbReport{}
|
||||
if !row.Next() {
|
||||
return reports.ReportConfig{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
if err := row.StructScan(&dbreport); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
cfg, err := dbToReport(dbreport)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportConfigStatus(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
q := `UPDATE report_config SET status = :status, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id
|
||||
|
||||
@@ -74,7 +74,11 @@ func TestMain(m *testing.M) {
|
||||
SSLRootCert: "",
|
||||
}
|
||||
|
||||
if db, err = postgres.Setup(dbConfig, *rpostgres.Migration()); err != nil {
|
||||
migration, err := rpostgres.Migration()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get migration: %s", err)
|
||||
}
|
||||
if db, err = postgres.Setup(dbConfig, *migration); err != nil {
|
||||
log.Fatalf("Could not setup test DB connection: %s", err)
|
||||
}
|
||||
|
||||
|
||||
+20
-15
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/transformers/senml"
|
||||
)
|
||||
|
||||
@@ -152,20 +153,21 @@ func (rm ReqMetric) Validate() error {
|
||||
}
|
||||
|
||||
type ReportConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DomainID string `json:"domain_id"`
|
||||
Schedule schedule.Schedule `json:"schedule,omitempty"`
|
||||
Config *MetricConfig `json:"config,omitempty"`
|
||||
Email *EmailSetting `json:"email,omitempty"`
|
||||
Metrics []ReqMetric `json:"metrics,omitempty"`
|
||||
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DomainID string `json:"domain_id"`
|
||||
Schedule schedule.Schedule `json:"schedule,omitempty"`
|
||||
Config *MetricConfig `json:"config,omitempty"`
|
||||
Email *EmailSetting `json:"email,omitempty"`
|
||||
Metrics []ReqMetric `json:"metrics,omitempty"`
|
||||
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
Roles []roles.MemberRoleActions `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
type ReportConfigPage struct {
|
||||
@@ -398,6 +400,7 @@ type PageMeta struct {
|
||||
type Repository interface {
|
||||
AddReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, id string) (ReportConfig, error)
|
||||
RetrieveByIDWithRoles(ctx context.Context, id, memberID string) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, id string) error
|
||||
@@ -408,11 +411,12 @@ type Repository interface {
|
||||
UpdateReportTemplate(ctx context.Context, domainID, reportID string, template ReportTemplate) error
|
||||
ViewReportTemplate(ctx context.Context, domainID, reportID string) (ReportTemplate, error)
|
||||
DeleteReportTemplate(ctx context.Context, domainID, reportID string) error
|
||||
roles.Repository
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, session authn.Session, id string) error
|
||||
@@ -426,4 +430,5 @@ type Service interface {
|
||||
|
||||
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
|
||||
StartScheduler(ctx context.Context) error
|
||||
roles.RoleManager
|
||||
}
|
||||
|
||||
+56
-13
@@ -15,10 +15,13 @@ import (
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/reltime"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/magistrala/reports/operations"
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/transformers/senml"
|
||||
)
|
||||
|
||||
@@ -33,22 +36,28 @@ type report struct {
|
||||
readers grpcReadersV1.ReadersServiceClient
|
||||
defaultTemplate ReportTemplate
|
||||
converterURL string
|
||||
roles.ProvisionManageService
|
||||
}
|
||||
|
||||
func NewService(repo Repository, runInfo chan pkglog.RunInfo, idp supermq.IDProvider, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient, template ReportTemplate, converterURL string) Service {
|
||||
return &report{
|
||||
repo: repo,
|
||||
idp: idp,
|
||||
runInfo: runInfo,
|
||||
email: emailer,
|
||||
ticker: tck,
|
||||
readers: readers,
|
||||
defaultTemplate: template,
|
||||
converterURL: converterURL,
|
||||
func NewService(repo Repository, runInfo chan pkglog.RunInfo, policy policies.Service, idp supermq.IDProvider, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient, template ReportTemplate, converterURL string, availableActions []roles.Action, builtInRoles map[roles.BuiltInRoleName][]roles.Action) (Service, error) {
|
||||
rpms, err := roles.NewProvisionManageService(operations.EntityType, repo, policy, idp, availableActions, builtInRoles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &report{
|
||||
repo: repo,
|
||||
idp: idp,
|
||||
runInfo: runInfo,
|
||||
email: emailer,
|
||||
ticker: tck,
|
||||
readers: readers,
|
||||
defaultTemplate: template,
|
||||
converterURL: converterURL,
|
||||
ProvisionManageService: rpms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *report) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
func (r *report) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (retCfg ReportConfig, retErr error) {
|
||||
id, err := r.idp.ID()
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
@@ -71,11 +80,45 @@ func (r *report) AddReportConfig(ctx context.Context, session authn.Session, cfg
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if errRollBack := r.repo.RemoveReportConfig(ctx, reportConfig.ID); errRollBack != nil {
|
||||
retErr = errors.Wrap(retErr, errors.Wrap(svcerr.ErrRollbackRepo, errRollBack))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
newBuiltInRoleMembers := map[roles.BuiltInRoleName][]roles.Member{
|
||||
BuiltInRoleAdmin: {roles.Member(session.UserID)},
|
||||
}
|
||||
|
||||
optionalPolicies := []policies.Policy{
|
||||
{
|
||||
SubjectType: policies.DomainType,
|
||||
Subject: session.DomainID,
|
||||
Relation: policies.DomainRelation,
|
||||
ObjectType: operations.EntityType,
|
||||
Object: reportConfig.ID,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = r.AddNewEntitiesRoles(ctx, session.DomainID, session.UserID, []string{reportConfig.ID}, optionalPolicies, newBuiltInRoleMembers)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrAddPolicies, err)
|
||||
}
|
||||
|
||||
return reportConfig, nil
|
||||
}
|
||||
|
||||
func (r *report) ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
cfg, err := r.repo.ViewReportConfig(ctx, id)
|
||||
func (r *report) ViewReportConfig(ctx context.Context, session authn.Session, id string, withRoles bool) (ReportConfig, error) {
|
||||
var cfg ReportConfig
|
||||
var err error
|
||||
switch withRoles {
|
||||
case true:
|
||||
cfg, err = r.repo.RetrieveByIDWithRoles(ctx, id, session.UserID)
|
||||
default:
|
||||
cfg, err = r.repo.ViewReportConfig(ctx, id)
|
||||
}
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
+125
-29
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
policymocks "github.com/absmach/supermq/pkg/policies/mocks"
|
||||
"github.com/absmach/supermq/pkg/roles"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -52,24 +54,39 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func newService(runInfo chan pkglog.RunInfo) (reports.Service, *mocks.Repository, *tmocks.Ticker) {
|
||||
func newService(t *testing.T, runInfo chan pkglog.RunInfo) (reports.Service, *mocks.Repository, *tmocks.Ticker, *policymocks.Service) {
|
||||
repo := new(mocks.Repository)
|
||||
mockTicker := new(tmocks.Ticker)
|
||||
idProvider := uuid.NewMock()
|
||||
readersSvc := new(readmocks.ReadersServiceClient)
|
||||
e := new(emocks.Emailer)
|
||||
return reports.NewService(repo, runInfo, idProvider, mockTicker, e, readersSvc, template, ""), repo, mockTicker
|
||||
policy := new(policymocks.Service)
|
||||
|
||||
availableActions := []roles.Action{}
|
||||
builtInRoles := map[roles.BuiltInRoleName][]roles.Action{
|
||||
"admin": availableActions,
|
||||
}
|
||||
|
||||
svc, err := reports.NewService(repo, runInfo, policy, idProvider, mockTicker, e, readersSvc, template, "", availableActions, builtInRoles)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create service: %v", err)
|
||||
}
|
||||
return svc, repo, mockTicker, policy
|
||||
}
|
||||
|
||||
func TestAddReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, policies := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg reports.ReportConfig
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg reports.ReportConfig
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
addPoliciesErr error
|
||||
deletePolicies error
|
||||
addRoleErr error
|
||||
deleteErr error
|
||||
}{
|
||||
{
|
||||
desc: "Add report config successfully",
|
||||
@@ -81,8 +98,11 @@ func TestAddReportConfig(t *testing.T) {
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
addPoliciesErr: nil,
|
||||
addRoleErr: nil,
|
||||
deleteErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed repo",
|
||||
@@ -94,13 +114,79 @@ func TestAddReportConfig(t *testing.T) {
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
err: repoerr.ErrCreateEntity,
|
||||
addPoliciesErr: nil,
|
||||
deletePolicies: nil,
|
||||
addRoleErr: nil,
|
||||
deleteErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed to add policies",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
addPoliciesErr: svcerr.ErrAuthorization,
|
||||
err: svcerr.ErrAddPolicies,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed to add policies and failed rollback",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
addPoliciesErr: svcerr.ErrAuthorization,
|
||||
deleteErr: svcerr.ErrRemoveEntity,
|
||||
err: svcerr.ErrRollbackRepo,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed to add roles",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
addRoleErr: svcerr.ErrCreateEntity,
|
||||
err: svcerr.ErrAddPolicies,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed to add roles and failed to delete policies",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
addRoleErr: svcerr.ErrCreateEntity,
|
||||
deletePolicies: svcerr.ErrRemoveEntity,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
policyCall := policies.On("AddPolicies", context.Background(), mock.Anything).Return(tc.addPoliciesErr)
|
||||
policyCall2 := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePolicies).Maybe()
|
||||
repoCall1 := repo.On("AddRoles", context.Background(), mock.Anything).Return([]roles.RoleProvision{}, tc.addRoleErr)
|
||||
repoCall2 := repo.On("Remove", context.Background(), mock.Anything).Return(tc.deleteErr).Maybe()
|
||||
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
@@ -108,13 +194,17 @@ func TestAddReportConfig(t *testing.T) {
|
||||
assert.Equal(t, tc.cfg.Name, res.Name)
|
||||
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
policyCall.Unset()
|
||||
policyCall2.Unset()
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
repoCall2.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -147,7 +237,7 @@ func TestViewReportConfig(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id)
|
||||
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id, false)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
@@ -158,7 +248,7 @@ func TestViewReportConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
newName := namegen.Generate()
|
||||
now := time.Now().Add(time.Hour)
|
||||
@@ -220,7 +310,7 @@ func TestUpdateReportConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListReportsConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
numConfigs := 50
|
||||
now := time.Now().Add(time.Hour)
|
||||
var configs []reports.ReportConfig
|
||||
@@ -325,13 +415,14 @@ func TestListReportsConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, policies := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
err error
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
err error
|
||||
deletePoliciesErr error
|
||||
}{
|
||||
{
|
||||
desc: "remove report config successfully",
|
||||
@@ -339,8 +430,9 @@ func TestRemoveReportConfig(t *testing.T) {
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: nil,
|
||||
id: rptConfig.ID,
|
||||
err: nil,
|
||||
deletePoliciesErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove report config with failed repo",
|
||||
@@ -348,24 +440,27 @@ func TestRemoveReportConfig(t *testing.T) {
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
deletePoliciesErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
|
||||
policyCall := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePoliciesErr)
|
||||
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
defer repoCall.Unset()
|
||||
policyCall.Unset()
|
||||
repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -413,7 +508,7 @@ func TestEnableReportConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDisableReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -469,7 +564,8 @@ func TestDisableReportConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateInstantEmailReport(t *testing.T) {
|
||||
svc, _, _ := newService(make(chan pkglog.RunInfo))
|
||||
// nolint:dogsled
|
||||
svc, _, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
validEmailConfig := reports.EmailSetting{
|
||||
To: []string{"test@example.com"},
|
||||
|
||||
Reference in New Issue
Block a user