mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 06:20:18 +00:00
NOISSUE - Update auth in journal service (#2527)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
+65
-7
@@ -27,17 +27,56 @@ tags:
|
||||
url: http://docs.mainflux.io/
|
||||
|
||||
paths:
|
||||
/journal/{entity_type}/{id}:
|
||||
/journal/user/{userID}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List journal log
|
||||
summary: List user journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/user_id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
- $ref: "#/components/parameters/limit"
|
||||
- $ref: "#/components/parameters/operation"
|
||||
- $ref: "#/components/parameters/with_attributes"
|
||||
- $ref: "#/components/parameters/with_metadata"
|
||||
- $ref: "#/components/parameters/from"
|
||||
- $ref: "#/components/parameters/to"
|
||||
- $ref: "#/components/parameters/dir"
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/JournalsPageRes"
|
||||
"400":
|
||||
description: Failed due to malformed query parameters.
|
||||
"401":
|
||||
description: Missing or invalid access token provided.
|
||||
"403":
|
||||
description: Failed to perform authorization over the entity.
|
||||
"404":
|
||||
description: A non-existent entity request.
|
||||
"422":
|
||||
description: Database can't process request.
|
||||
"500":
|
||||
$ref: "#/components/responses/ServiceError"
|
||||
|
||||
/{domainID}/journal/{entityType}/{id}:
|
||||
get:
|
||||
tags:
|
||||
- journal-log
|
||||
summary: List entity journal log
|
||||
description: |
|
||||
Retrieves a list of journal. Due to performance concerns, data
|
||||
is retrieved in subsets. The API must ensure that the entire
|
||||
dataset is consumed either by making subsequent requests, or by
|
||||
increasing the subset size of the initial request.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/domain_id"
|
||||
- $ref: "#/components/parameters/entity_type"
|
||||
- $ref: "#/components/parameters/id"
|
||||
- $ref: "#/components/parameters/offset"
|
||||
@@ -146,23 +185,42 @@ components:
|
||||
example: { "error": "malformed entity specification" }
|
||||
|
||||
parameters:
|
||||
domain_id:
|
||||
name: domainID
|
||||
description: Unique identifier for a domain.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
entity_type:
|
||||
name: entity_type
|
||||
description: Type of entity, e.g. user, group, thing, etc.
|
||||
name: entityType
|
||||
description: Type of entity, e.g. user, group, thing, etc.entityType
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- user
|
||||
- group
|
||||
- thing
|
||||
- channel
|
||||
required: true
|
||||
example: user
|
||||
example: group
|
||||
|
||||
user_id:
|
||||
name: userID
|
||||
description: Unique identifier for a user.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
required: true
|
||||
example: bb7edb32-2eac-4aad-aebe-ed96fe073879
|
||||
|
||||
id:
|
||||
name: id
|
||||
description: Unique identifier for an entity, e.g. user, group, domain, etc. Used together with entity_type.
|
||||
description: Unique identifier for an entity, e.g. group, channel or thing. Used together with entity_type.
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
|
||||
+12
-6
@@ -9,24 +9,30 @@ import (
|
||||
)
|
||||
|
||||
var cmdJournal = cobra.Command{
|
||||
Use: "get <entity_type> <entity_id> <user_auth_token>",
|
||||
Use: "get <entity_type> <entity_id> <domain_id> <user_auth_token>",
|
||||
Short: "Get journal",
|
||||
Long: "Get journal\n" +
|
||||
"Usage:\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <user_auth_token> - lists journal logs\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <user_auth_token> --offset <offset> --limit <limit> - lists journal logs with provided offset and limit\n",
|
||||
"\tmagistrala-cli journal get user <user_id> <user_auth_token> - lists user journal logs\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <domain_id> <user_auth_token> - lists entity journal logs\n" +
|
||||
"\tmagistrala-cli journal get <entity_type> <entity_id> <domain_id> <user_auth_token> --offset <offset> --limit <limit> - lists user journal logs with provided offset and limit\n",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
if len(args) < 3 || len(args) > 4 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
pageMetadata := mgxsdk.PageMetadata{
|
||||
Offset: Offset,
|
||||
Limit: Limit,
|
||||
}
|
||||
|
||||
journal, err := sdk.Journal(args[0], args[1], pageMetadata, args[2])
|
||||
entityType, entityID, token := args[0], args[1], args[2]
|
||||
domainID := ""
|
||||
if len(args) == 4 {
|
||||
entityType, entityID, domainID, token = args[0], args[1], args[2], args[3]
|
||||
}
|
||||
|
||||
journal, err := sdk.Journal(entityType, entityID, domainID, pageMetadata, token)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
|
||||
+26
-5
@@ -31,8 +31,9 @@ func TestGetJournalCmd(t *testing.T) {
|
||||
rootCmd := setFlags(invCmd)
|
||||
|
||||
var page mgsdk.JournalsPage
|
||||
entityType := "entity_type"
|
||||
entityId := journal.ID
|
||||
entityType := "group"
|
||||
entityId := testsutil.GenerateUUID(t)
|
||||
domainId := testsutil.GenerateUUID(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -43,10 +44,26 @@ func TestGetJournalCmd(t *testing.T) {
|
||||
errLogMessage string
|
||||
}{
|
||||
{
|
||||
desc: "get journal with journal id",
|
||||
desc: "get user journal",
|
||||
args: []string{
|
||||
"user",
|
||||
entityId,
|
||||
token,
|
||||
},
|
||||
logType: entityLog,
|
||||
page: mgsdk.JournalsPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Journals: []mgsdk.Journal{journal},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "get group journal",
|
||||
args: []string{
|
||||
entityType,
|
||||
entityId,
|
||||
domainId,
|
||||
token,
|
||||
},
|
||||
logType: entityLog,
|
||||
@@ -63,6 +80,7 @@ func TestGetJournalCmd(t *testing.T) {
|
||||
entityType,
|
||||
entityId,
|
||||
token,
|
||||
domainId,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
@@ -72,6 +90,7 @@ func TestGetJournalCmd(t *testing.T) {
|
||||
args: []string{
|
||||
entityType,
|
||||
entityId,
|
||||
domainId,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
@@ -82,8 +101,10 @@ func TestGetJournalCmd(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("Journal", tc.args[0], tc.args[1], mock.Anything, tc.args[2]).Return(tc.page, tc.sdkErr)
|
||||
|
||||
sdkCall := sdkMock.On("Journal", tc.args[0], tc.args[1], "", mock.Anything, tc.args[2]).Return(tc.page, tc.sdkErr)
|
||||
if tc.args[0] != "user" {
|
||||
sdkCall = sdkMock.On("Journal", tc.args[0], tc.args[1], tc.args[2], mock.Anything, tc.args[3]).Return(tc.page, tc.sdkErr)
|
||||
}
|
||||
out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
|
||||
+5
-5
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/absmach/magistrala/journal/middleware"
|
||||
journalpg "github.com/absmach/magistrala/journal/postgres"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc"
|
||||
mgauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
authsvcAuthz "github.com/absmach/magistrala/pkg/authz/authsvc"
|
||||
@@ -134,7 +133,7 @@ func main() {
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
svc := newService(db, dbConfig, authn, authz, logger, tracer)
|
||||
svc := newService(db, dbConfig, authz, logger, tracer)
|
||||
|
||||
subscriber, err := store.NewSubscriber(ctx, cfg.ESURL, logger)
|
||||
if err != nil {
|
||||
@@ -158,7 +157,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
hs := http.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, logger, svcName, cfg.InstanceID), logger)
|
||||
hs := http.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(svc, authn, logger, svcName, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, magistrala.Version, logger, cancel)
|
||||
@@ -178,12 +177,13 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db *sqlx.DB, dbConfig pgclient.Config, authn mgauthn.Authentication, authz mgauthz.Authorization, logger *slog.Logger, tracer trace.Tracer) journal.Service {
|
||||
func newService(db *sqlx.DB, dbConfig pgclient.Config, authz mgauthz.Authorization, logger *slog.Logger, tracer trace.Tracer) journal.Service {
|
||||
database := postgres.NewDatabase(db, dbConfig, tracer)
|
||||
repo := journalpg.NewRepository(database)
|
||||
idp := uuid.New()
|
||||
|
||||
svc := journal.NewService(authn, authz, idp, repo)
|
||||
svc := journal.NewService(idp, repo)
|
||||
svc = middleware.AuthorizationMiddleware(svc, authz)
|
||||
svc = middleware.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics("journal", "journal_writer")
|
||||
svc = middleware.MetricsMiddleware(svc, counter, latency)
|
||||
|
||||
@@ -6,9 +6,12 @@ package api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/internal/api"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
"github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
@@ -19,7 +22,12 @@ func retrieveJournalsEndpoint(svc journal.Service) endpoint.Endpoint {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
page, err := svc.RetrieveAll(ctx, req.token, req.page)
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
page, err := svc.RetrieveAll(ctx, session, req.page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+143
-21
@@ -12,11 +12,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
"github.com/absmach/magistrala/journal/api"
|
||||
"github.com/absmach/magistrala/journal/mocks"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -45,20 +48,22 @@ func (tr testRequest) make() (*http.Response, error) {
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newjournalServer() (*httptest.Server, *mocks.Service) {
|
||||
func newjournalServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
|
||||
logger := mglog.NewMock()
|
||||
mux := api.MakeHandler(svc, logger, "journal-log", "test")
|
||||
return httptest.NewServer(mux), svc
|
||||
authn := new(authnmocks.Authentication)
|
||||
mux := api.MakeHandler(svc, authn, logger, "journal-log", "test")
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func TestListJournalsEndpoint(t *testing.T) {
|
||||
es, svc := newjournalServer()
|
||||
func TestListUserJournalsEndpoint(t *testing.T) {
|
||||
es, svc, authn := newjournalServer()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
url string
|
||||
contentType string
|
||||
status int
|
||||
@@ -225,20 +230,6 @@ func TestListJournalsEndpoint(t *testing.T) {
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid entity type",
|
||||
token: validToken,
|
||||
url: "/invalid/123",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with all query params",
|
||||
token: validToken,
|
||||
url: "/user/123?offset=10&limit=10&operation=user.create&from=0&to=10&with_attributes=true&with_metadata=true&dir=asc",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty url",
|
||||
token: validToken,
|
||||
@@ -250,7 +241,7 @@ func TestListJournalsEndpoint(t *testing.T) {
|
||||
desc: "with empty entity type",
|
||||
token: validToken,
|
||||
url: "//123",
|
||||
status: http.StatusBadRequest,
|
||||
status: http.StatusNotFound,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -264,7 +255,13 @@ func TestListJournalsEndpoint(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, c.token, mock.Anything).Return(journal.JournalsPage{}, c.svcErr)
|
||||
if c.token == validToken {
|
||||
c.session = mgauthn.Session{
|
||||
UserID: testsutil.GenerateUUID(t),
|
||||
}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, nil)
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, c.session, mock.Anything).Return(journal.JournalsPage{}, c.svcErr)
|
||||
req := testRequest{
|
||||
client: es.Client(),
|
||||
method: http.MethodGet,
|
||||
@@ -277,6 +274,131 @@ func TestListJournalsEndpoint(t *testing.T) {
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, c.status, resp.StatusCode, c.desc)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEntityJournalsEndpoint(t *testing.T) {
|
||||
es, svc, authn := newjournalServer()
|
||||
|
||||
domainID := testsutil.GenerateUUID(t)
|
||||
userID := testsutil.GenerateUUID(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
domainID string
|
||||
url string
|
||||
contentType string
|
||||
status int
|
||||
authnErr error
|
||||
svcErr error
|
||||
}{
|
||||
{
|
||||
desc: "with group type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/group/123",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with channel type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/channel/123",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with thing type successful",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/thing/123",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/thing/123",
|
||||
status: http.StatusForbidden,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "with operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/channel/123?operation=channel.create",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with malformed operation",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/user/123?operation=user.create&operation=user.update",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with invalid entity type",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/invalid/123",
|
||||
status: http.StatusBadRequest,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with all query params",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
url: "/group/123?offset=10&limit=10&operation=group.create&from=0&to=10&with_attributes=true&with_metadata=true&dir=asc",
|
||||
status: http.StatusOK,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: " with empty token",
|
||||
url: "/group/123",
|
||||
domainID: domainID,
|
||||
status: http.StatusUnauthorized,
|
||||
svcErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "with empty domain ID",
|
||||
token: validToken,
|
||||
url: "/group/",
|
||||
status: http.StatusNotFound,
|
||||
svcErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.token == validToken {
|
||||
c.session = mgauthn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
DomainUserID: domainID + "_" + userID,
|
||||
}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, c.token).Return(c.session, c.authnErr)
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, c.session, mock.Anything).Return(journal.JournalsPage{}, c.svcErr)
|
||||
req := testRequest{
|
||||
client: es.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/journal%s", es.URL, c.domainID, c.url),
|
||||
token: c.token,
|
||||
}
|
||||
resp, err := req.make()
|
||||
assert.Nil(t, err, c.desc)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, c.status, resp.StatusCode, c.desc)
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+73
-40
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/absmach/magistrala/internal/api"
|
||||
"github.com/absmach/magistrala/journal"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
@@ -33,19 +34,26 @@ const (
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP API handler with health check and metrics.
|
||||
func MakeHandler(svc journal.Service, logger *slog.Logger, svcName, instanceID string) http.Handler {
|
||||
func MakeHandler(svc journal.Service, authn mgauthn.Authentication, logger *slog.Logger, svcName, instanceID string) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
mux.Get("/journal/{entityType}/{entityID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
mux.With(api.AuthenticateMiddleware(authn, false)).Get("/journal/user/{userID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
retrieveJournalsEndpoint(svc),
|
||||
decodeRetrieveJournalReq,
|
||||
decodeRetrieveUserJournalReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list_journals").ServeHTTP)
|
||||
), "list_user_journals").ServeHTTP)
|
||||
|
||||
mux.With(api.AuthenticateMiddleware(authn, true)).Get("/{domainID}/journal/{entityType}/{entityID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
retrieveJournalsEndpoint(svc),
|
||||
decodeRetrieveEntityJournalReq,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list__entity_journals").ServeHTTP)
|
||||
|
||||
mux.Get("/health", magistrala.Health(svcName, instanceID))
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
@@ -53,25 +61,65 @@ func MakeHandler(svc journal.Service, logger *slog.Logger, svcName, instanceID s
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeRetrieveJournalReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
|
||||
func decodeRetrieveEntityJournalReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
page, err := decodePageQuery(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
entityType, err := journal.ToEntityType(chi.URLParam(r, "entityType"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
page.EntityID = chi.URLParam(r, "entityID")
|
||||
page.EntityType = entityType
|
||||
|
||||
if entityType == journal.ChannelEntity {
|
||||
page.Operation = strings.ReplaceAll(page.Operation, "channel", "group")
|
||||
}
|
||||
|
||||
req := retrieveJournalsReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
page: page,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeRetrieveUserJournalReq(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
page, err := decodePageQuery(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
page.EntityID = chi.URLParam(r, "userID")
|
||||
page.EntityType = journal.UserEntity
|
||||
|
||||
req := retrieveJournalsReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
page: page,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodePageQuery(r *http.Request) (journal.Page, error) {
|
||||
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
|
||||
if err != nil {
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
operation, err := apiutil.ReadStringQuery(r, operationKey, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
from, err := apiutil.ReadNumQuery[int64](r, fromKey, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
if from > math.MaxInt32 {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat)
|
||||
}
|
||||
var fromTime time.Time
|
||||
if from != 0 {
|
||||
@@ -79,10 +127,10 @@ func decodeRetrieveJournalReq(_ context.Context, r *http.Request) (interface{},
|
||||
}
|
||||
to, err := apiutil.ReadNumQuery[int64](r, toKey, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
if to > math.MaxInt32 {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat)
|
||||
}
|
||||
var toTime time.Time
|
||||
if to != 0 {
|
||||
@@ -90,40 +138,25 @@ func decodeRetrieveJournalReq(_ context.Context, r *http.Request) (interface{},
|
||||
}
|
||||
attributes, err := apiutil.ReadBoolQuery(r, attributesKey, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
metadata, err := apiutil.ReadBoolQuery(r, metadataKey, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DescDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
entityType, err := journal.ToEntityType(chi.URLParam(r, "entityType"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
if entityType == journal.ChannelEntity {
|
||||
operation = strings.ReplaceAll(operation, "channel", "group")
|
||||
}
|
||||
|
||||
req := retrieveJournalsReq{
|
||||
token: apiutil.ExtractBearerToken(r),
|
||||
page: journal.Page{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Operation: operation,
|
||||
From: fromTime,
|
||||
To: toTime,
|
||||
WithAttributes: attributes,
|
||||
WithMetadata: metadata,
|
||||
EntityID: chi.URLParam(r, "entityID"),
|
||||
EntityType: entityType,
|
||||
Direction: dir,
|
||||
},
|
||||
}
|
||||
|
||||
return req, nil
|
||||
return journal.Page{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Operation: operation,
|
||||
From: fromTime,
|
||||
To: toTime,
|
||||
WithAttributes: attributes,
|
||||
WithMetadata: metadata,
|
||||
Direction: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"github.com/absmach/magistrala/journal"
|
||||
aevents "github.com/absmach/magistrala/journal/events"
|
||||
"github.com/absmach/magistrala/journal/mocks"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
authzmocks "github.com/absmach/magistrala/pkg/authz/mocks"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -53,9 +51,7 @@ func NewTestEvent(data map[string]interface{}, err error) testEvent {
|
||||
|
||||
func TestHandle(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
authn := new(authnmocks.Authentication)
|
||||
authz := new(authzmocks.Authorization)
|
||||
svc := journal.NewService(authn, authz, idProvider, repo)
|
||||
svc := journal.NewService(idProvider, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
)
|
||||
|
||||
@@ -143,7 +144,7 @@ type Service interface {
|
||||
Save(ctx context.Context, journal Journal) error
|
||||
|
||||
// RetrieveAll retrieves all journals from the database with the given page.
|
||||
RetrieveAll(ctx context.Context, token string, page Page) (JournalsPage, error)
|
||||
RetrieveAll(ctx context.Context, session mgauthn.Session, page Page) (JournalsPage, error)
|
||||
}
|
||||
|
||||
// Repository provides access to the journal log database.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/journal"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
mgauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
)
|
||||
|
||||
var _ journal.Service = (*authorizationMiddleware)(nil)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc journal.Service
|
||||
authz mgauthz.Authorization
|
||||
}
|
||||
|
||||
// AuthorizationMiddleware adds authorization to the journal service.
|
||||
func AuthorizationMiddleware(svc journal.Service, authz mgauthz.Authorization) journal.Service {
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
}
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) Save(ctx context.Context, journal journal.Journal) error {
|
||||
return am.svc.Save(ctx, journal)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journal.JournalsPage, error) {
|
||||
permission := policies.ViewPermission
|
||||
objectType := page.EntityType.AuthString()
|
||||
object := page.EntityID
|
||||
subject := session.DomainUserID
|
||||
|
||||
// If the entity is a user, we need to check if the user is an admin
|
||||
if page.EntityType.AuthString() == policies.UserType {
|
||||
permission = policies.AdminPermission
|
||||
objectType = policies.PlatformType
|
||||
object = policies.MagistralaObject
|
||||
subject = session.UserID
|
||||
}
|
||||
|
||||
req := mgauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: subject,
|
||||
Permission: permission,
|
||||
ObjectType: objectType,
|
||||
Object: object,
|
||||
}
|
||||
if err := am.authz.Authorize(ctx, req); err != nil {
|
||||
return journal.JournalsPage{}, err
|
||||
}
|
||||
|
||||
return am.svc.RetrieveAll(ctx, session, page)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/journal"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
)
|
||||
|
||||
var _ journal.Service = (*loggingMiddleware)(nil)
|
||||
@@ -46,7 +47,7 @@ func (lm *loggingMiddleware) Save(ctx context.Context, j journal.Journal) (err e
|
||||
return lm.service.Save(ctx, j)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) RetrieveAll(ctx context.Context, token string, page journal.Page) (journalsPage journal.JournalsPage, err error) {
|
||||
func (lm *loggingMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journalsPage journal.JournalsPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
@@ -66,5 +67,5 @@ func (lm *loggingMiddleware) RetrieveAll(ctx context.Context, token string, page
|
||||
lm.logger.Info("Retrieve all journals completed successfully", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.service.RetrieveAll(ctx, token, page)
|
||||
return lm.service.RetrieveAll(ctx, session, page)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/journal"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
@@ -38,11 +39,11 @@ func (mm *metricsMiddleware) Save(ctx context.Context, j journal.Journal) error
|
||||
return mm.service.Save(ctx, j)
|
||||
}
|
||||
|
||||
func (mm *metricsMiddleware) RetrieveAll(ctx context.Context, token string, page journal.Page) (journal.JournalsPage, error) {
|
||||
func (mm *metricsMiddleware) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (journal.JournalsPage, error) {
|
||||
defer func(begin time.Time) {
|
||||
mm.counter.With("method", "retrieve_all").Add(1)
|
||||
mm.latency.With("method", "retrieve_all").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return mm.service.RetrieveAll(ctx, token, page)
|
||||
return mm.service.RetrieveAll(ctx, session, page)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/journal"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -32,7 +33,7 @@ func (tm *tracing) Save(ctx context.Context, j journal.Journal) error {
|
||||
return tm.svc.Save(ctx, j)
|
||||
}
|
||||
|
||||
func (tm *tracing) RetrieveAll(ctx context.Context, token string, page journal.Page) (resp journal.JournalsPage, err error) {
|
||||
func (tm *tracing) RetrieveAll(ctx context.Context, session mgauthn.Session, page journal.Page) (resp journal.JournalsPage, err error) {
|
||||
ctx, span := tm.tracer.Start(ctx, "retrieve_all", trace.WithAttributes(
|
||||
attribute.Int64("offset", int64(page.Offset)),
|
||||
attribute.Int64("limit", int64(page.Limit)),
|
||||
@@ -42,5 +43,5 @@ func (tm *tracing) RetrieveAll(ctx context.Context, token string, page journal.P
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.RetrieveAll(ctx, token, page)
|
||||
return tm.svc.RetrieveAll(ctx, session, page)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ package mocks
|
||||
import (
|
||||
context "context"
|
||||
|
||||
authn "github.com/absmach/magistrala/pkg/authn"
|
||||
|
||||
journal "github.com/absmach/magistrala/journal"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@@ -16,9 +19,9 @@ type Service struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// RetrieveAll provides a mock function with given fields: ctx, token, page
|
||||
func (_m *Service) RetrieveAll(ctx context.Context, token string, page journal.Page) (journal.JournalsPage, error) {
|
||||
ret := _m.Called(ctx, token, page)
|
||||
// RetrieveAll provides a mock function with given fields: ctx, session, page
|
||||
func (_m *Service) RetrieveAll(ctx context.Context, session authn.Session, page journal.Page) (journal.JournalsPage, error) {
|
||||
ret := _m.Called(ctx, session, page)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RetrieveAll")
|
||||
@@ -26,17 +29,17 @@ func (_m *Service) RetrieveAll(ctx context.Context, token string, page journal.P
|
||||
|
||||
var r0 journal.JournalsPage
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, journal.Page) (journal.JournalsPage, error)); ok {
|
||||
return rf(ctx, token, page)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, journal.Page) (journal.JournalsPage, error)); ok {
|
||||
return rf(ctx, session, page)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, journal.Page) journal.JournalsPage); ok {
|
||||
r0 = rf(ctx, token, page)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, authn.Session, journal.Page) journal.JournalsPage); ok {
|
||||
r0 = rf(ctx, session, page)
|
||||
} else {
|
||||
r0 = ret.Get(0).(journal.JournalsPage)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, journal.Page) error); ok {
|
||||
r1 = rf(ctx, token, page)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, authn.Session, journal.Page) error); ok {
|
||||
r1 = rf(ctx, session, page)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ var (
|
||||
"user_id": entityID,
|
||||
"metadata": payload,
|
||||
}
|
||||
validTimeStamp = time.Now().UTC().Truncate(time.Millisecond)
|
||||
)
|
||||
|
||||
func TestJournalSave(t *testing.T) {
|
||||
@@ -689,6 +690,8 @@ func TestJournalRetrieveAll(t *testing.T) {
|
||||
page.Journals[i].Attributes = map[string]interface{}{}
|
||||
tc.response.Journals[i].Metadata = map[string]interface{}{}
|
||||
page.Journals[i].Metadata = map[string]interface{}{}
|
||||
tc.response.Journals[i].OccurredAt = validTimeStamp
|
||||
page.Journals[i].OccurredAt = validTimeStamp
|
||||
}
|
||||
assert.ElementsMatch(t, tc.response.Journals, page.Journals)
|
||||
|
||||
|
||||
+7
-46
@@ -8,22 +8,18 @@ import (
|
||||
|
||||
"github.com/absmach/magistrala"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
mgauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
authn mgauthn.Authentication
|
||||
authz mgauthz.Authorization
|
||||
idProvider magistrala.IDProvider
|
||||
repository Repository
|
||||
}
|
||||
|
||||
func NewService(authn mgauthn.Authentication, authz mgauthz.Authorization, idp magistrala.IDProvider, repository Repository) Service {
|
||||
func NewService(idp magistrala.IDProvider, repository Repository) Service {
|
||||
return &service{
|
||||
idProvider: idp,
|
||||
authn: authn,
|
||||
authz: authz,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
@@ -38,46 +34,11 @@ func (svc *service) Save(ctx context.Context, journal Journal) error {
|
||||
return svc.repository.Save(ctx, journal)
|
||||
}
|
||||
|
||||
func (svc *service) RetrieveAll(ctx context.Context, token string, page Page) (JournalsPage, error) {
|
||||
if err := svc.authorize(ctx, token, page.EntityID, page.EntityType.AuthString()); err != nil {
|
||||
return JournalsPage{}, err
|
||||
}
|
||||
|
||||
return svc.repository.RetrieveAll(ctx, page)
|
||||
}
|
||||
|
||||
func (svc *service) authorize(ctx context.Context, token, entityID, entityType string) error {
|
||||
session, err := svc.authn.Authenticate(ctx, token)
|
||||
func (svc *service) RetrieveAll(ctx context.Context, session mgauthn.Session, page Page) (JournalsPage, error) {
|
||||
journalPage, err := svc.repository.RetrieveAll(ctx, page)
|
||||
if err != nil {
|
||||
return err
|
||||
return JournalsPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
permission := policies.ViewPermission
|
||||
objectType := entityType
|
||||
object := entityID
|
||||
subject := session.DomainUserID
|
||||
|
||||
// If the entity is a user, we need to check if the user is an admin
|
||||
if entityType == policies.UserType {
|
||||
permission = policies.AdminPermission
|
||||
objectType = policies.PlatformType
|
||||
object = policies.MagistralaObject
|
||||
subject = session.UserID
|
||||
}
|
||||
|
||||
req := mgauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: subject,
|
||||
Permission: permission,
|
||||
ObjectType: objectType,
|
||||
Object: object,
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return journalPage, nil
|
||||
}
|
||||
|
||||
+28
-89
@@ -14,13 +14,8 @@ import (
|
||||
"github.com/absmach/magistrala/journal"
|
||||
"github.com/absmach/magistrala/journal/mocks"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
mgauthz "github.com/absmach/magistrala/pkg/authz"
|
||||
authzmocks "github.com/absmach/magistrala/pkg/authz/mocks"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
"github.com/absmach/magistrala/pkg/policies"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -43,9 +38,7 @@ var (
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
authn := new(authnmocks.Authentication)
|
||||
authz := new(authzmocks.Authorization)
|
||||
svc := journal.NewService(authn, authz, idProvider, repo)
|
||||
svc := journal.NewService(idProvider, repo)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -78,11 +71,9 @@ func TestSave(t *testing.T) {
|
||||
|
||||
func TestReadAll(t *testing.T) {
|
||||
repo := new(mocks.Repository)
|
||||
authn := new(authnmocks.Authentication)
|
||||
authz := new(authzmocks.Authorization)
|
||||
svc := journal.NewService(authn, authz, idProvider, repo)
|
||||
svc := journal.NewService(idProvider, repo)
|
||||
|
||||
validToken := "token"
|
||||
validSession := mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t), DomainID: testsutil.GenerateUUID(t)}
|
||||
validPage := journal.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -91,34 +82,31 @@ func TestReadAll(t *testing.T) {
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
page journal.Page
|
||||
resp journal.JournalsPage
|
||||
identifyRes mgauthn.Session
|
||||
identifyErr error
|
||||
authErr error
|
||||
repoErr error
|
||||
err error
|
||||
desc string
|
||||
session mgauthn.Session
|
||||
page journal.Page
|
||||
resp journal.JournalsPage
|
||||
authErr error
|
||||
repoErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "successful",
|
||||
token: validToken,
|
||||
page: validPage,
|
||||
desc: "successful",
|
||||
session: validSession,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Journals: []journal.Journal{validJournal},
|
||||
},
|
||||
identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)},
|
||||
authErr: nil,
|
||||
repoErr: nil,
|
||||
err: nil,
|
||||
authErr: nil,
|
||||
repoErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "successful for user",
|
||||
token: validToken,
|
||||
desc: "successful for user",
|
||||
session: validSession,
|
||||
page: journal.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -131,78 +119,29 @@ func TestReadAll(t *testing.T) {
|
||||
Limit: 10,
|
||||
Journals: []journal.Journal{validJournal},
|
||||
},
|
||||
identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)},
|
||||
authErr: nil,
|
||||
repoErr: nil,
|
||||
err: nil,
|
||||
authErr: nil,
|
||||
repoErr: nil,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "with identify error",
|
||||
token: validToken,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{},
|
||||
identifyRes: mgauthn.Session{},
|
||||
identifyErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "with repo error",
|
||||
token: validToken,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{},
|
||||
identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)},
|
||||
repoErr: repoerr.ErrViewEntity,
|
||||
err: repoerr.ErrViewEntity,
|
||||
},
|
||||
{
|
||||
desc: "with failed to authorize",
|
||||
token: validToken,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{},
|
||||
identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)},
|
||||
authErr: svcerr.ErrAuthorization,
|
||||
repoErr: nil,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "with error on authorize",
|
||||
token: validToken,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{},
|
||||
identifyRes: mgauthn.Session{DomainUserID: testsutil.GenerateUUID(t), UserID: testsutil.GenerateUUID(t)},
|
||||
authErr: svcerr.ErrAuthorization,
|
||||
repoErr: nil,
|
||||
err: svcerr.ErrAuthorization,
|
||||
desc: "with repo error",
|
||||
session: validSession,
|
||||
page: validPage,
|
||||
resp: journal.JournalsPage{},
|
||||
repoErr: repoerr.ErrViewEntity,
|
||||
err: repoerr.ErrViewEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
authReq := mgauthz.PolicyReq{
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: tc.identifyRes.DomainUserID,
|
||||
ObjectType: tc.page.EntityType.AuthString(),
|
||||
Object: tc.page.EntityID,
|
||||
Permission: policies.ViewPermission,
|
||||
}
|
||||
if tc.page.EntityType == journal.UserEntity {
|
||||
authReq.Permission = policies.AdminPermission
|
||||
authReq.ObjectType = policies.PlatformType
|
||||
authReq.Object = policies.MagistralaObject
|
||||
authReq.Subject = tc.identifyRes.UserID
|
||||
}
|
||||
authCall := authn.On("Authenticate", context.Background(), tc.token).Return(tc.identifyRes, tc.identifyErr)
|
||||
authCall1 := authz.On("Authorize", context.Background(), authReq).Return(tc.authErr)
|
||||
repoCall := repo.On("RetrieveAll", context.Background(), tc.page).Return(tc.resp, tc.repoErr)
|
||||
resp, err := svc.RetrieveAll(context.Background(), tc.token, tc.page)
|
||||
resp, err := svc.RetrieveAll(context.Background(), tc.session, tc.page)
|
||||
if tc.err == nil {
|
||||
assert.Equal(t, tc.resp, resp, tc.desc)
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
repoCall.Unset()
|
||||
authCall.Unset()
|
||||
authCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type JournalsPage struct {
|
||||
Journals []Journal `json:"journals"`
|
||||
}
|
||||
|
||||
func (sdk mgSDK) Journal(entityType, entityID string, pm PageMetadata, token string) (journals JournalsPage, err error) {
|
||||
func (sdk mgSDK) Journal(entityType, entityID, domainID string, pm PageMetadata, token string) (journals JournalsPage, err error) {
|
||||
if entityID == "" {
|
||||
return JournalsPage{}, errors.NewSDKError(apiutil.ErrMissingID)
|
||||
}
|
||||
@@ -38,7 +38,12 @@ func (sdk mgSDK) Journal(entityType, entityID string, pm PageMetadata, token str
|
||||
return JournalsPage{}, errors.NewSDKError(apiutil.ErrMissingEntityType)
|
||||
}
|
||||
|
||||
url, err := sdk.withQueryParams(sdk.journalURL, fmt.Sprintf("%s/%s/%s", journalEndpoint, entityType, entityID), pm)
|
||||
reqUrl := fmt.Sprintf("%s/%s/%s/%s", domainID, journalEndpoint, entityType, entityID)
|
||||
if entityType == "user" {
|
||||
reqUrl = fmt.Sprintf("%s/%s/%s", journalEndpoint, entityType, entityID)
|
||||
}
|
||||
|
||||
url, err := sdk.withQueryParams(sdk.journalURL, reqUrl, pm)
|
||||
if err != nil {
|
||||
return JournalsPage{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
+117
-15
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/absmach/magistrala/journal/mocks"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/apiutil"
|
||||
mgauthn "github.com/absmach/magistrala/pkg/authn"
|
||||
authnmocks "github.com/absmach/magistrala/pkg/authn/mocks"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
svcerr "github.com/absmach/magistrala/pkg/errors/service"
|
||||
sdk "github.com/absmach/magistrala/pkg/sdk/go"
|
||||
@@ -22,20 +24,21 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func setupJournal() (*httptest.Server, *mocks.Service) {
|
||||
func setupJournal() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
|
||||
authn := new(authnmocks.Authentication)
|
||||
logger := mglog.NewMock()
|
||||
mux := api.MakeHandler(svc, logger, "journal-log", "test")
|
||||
return httptest.NewServer(mux), svc
|
||||
mux := api.MakeHandler(svc, authn, logger, "journal-log", "test")
|
||||
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func TestRetrieveJournal(t *testing.T) {
|
||||
js, svc := setupJournal()
|
||||
js, svc, authn := setupJournal()
|
||||
defer js.Close()
|
||||
|
||||
testJournal := generateTestJournal(t)
|
||||
validEntityType := "user"
|
||||
validEntityType := "group"
|
||||
|
||||
sdkConf := sdk.Config{
|
||||
JournalURL: js.URL,
|
||||
@@ -46,20 +49,24 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
session mgauthn.Session
|
||||
entityType string
|
||||
entityID string
|
||||
domainID string
|
||||
pageMeta sdk.PageMetadata
|
||||
svcReq journal.Page
|
||||
svcRes journal.JournalsPage
|
||||
svcErr error
|
||||
authnErr error
|
||||
response sdk.JournalsPage
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "retrieve journal successfully",
|
||||
desc: "retrieve user journal successfully",
|
||||
token: validToken,
|
||||
entityType: validEntityType,
|
||||
entityType: "user",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -82,6 +89,90 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve channel journal successfully",
|
||||
token: validToken,
|
||||
entityType: "channel",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
svcReq: journal.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
EntityID: validID,
|
||||
EntityType: journal.ChannelEntity,
|
||||
Direction: "desc",
|
||||
},
|
||||
svcRes: journal.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []journal.Journal{convertJournal(testJournal)},
|
||||
},
|
||||
svcErr: nil,
|
||||
response: sdk.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []sdk.Journal{testJournal},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve group journal successfully",
|
||||
token: validToken,
|
||||
entityType: "group",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
svcReq: journal.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
EntityID: validID,
|
||||
EntityType: journal.GroupEntity,
|
||||
Direction: "desc",
|
||||
},
|
||||
svcRes: journal.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []journal.Journal{convertJournal(testJournal)},
|
||||
},
|
||||
svcErr: nil,
|
||||
response: sdk.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []sdk.Journal{testJournal},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve thing journal successfully",
|
||||
token: validToken,
|
||||
entityType: "thing",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
svcReq: journal.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
EntityID: validID,
|
||||
EntityType: journal.ThingEntity,
|
||||
Direction: "desc",
|
||||
},
|
||||
svcRes: journal.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []journal.Journal{convertJournal(testJournal)},
|
||||
},
|
||||
svcErr: nil,
|
||||
response: sdk.JournalsPage{
|
||||
Total: 1,
|
||||
Journals: []sdk.Journal{testJournal},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve journal with invalid token",
|
||||
token: invalidToken,
|
||||
@@ -95,11 +186,11 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
EntityID: validID,
|
||||
EntityType: journal.UserEntity,
|
||||
EntityType: journal.GroupEntity,
|
||||
Direction: "desc",
|
||||
},
|
||||
svcRes: journal.JournalsPage{},
|
||||
svcErr: svcerr.ErrAuthentication,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
response: sdk.JournalsPage{},
|
||||
err: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, http.StatusUnauthorized),
|
||||
},
|
||||
@@ -116,13 +207,14 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
svcRes: journal.JournalsPage{},
|
||||
svcErr: nil,
|
||||
response: sdk.JournalsPage{},
|
||||
err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized),
|
||||
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
|
||||
},
|
||||
{
|
||||
desc: "retrieve journal with invalid entity type",
|
||||
token: validToken,
|
||||
entityType: "invalid",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -138,6 +230,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
token: validToken,
|
||||
entityType: validEntityType,
|
||||
entityID: "",
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -153,6 +246,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
token: validToken,
|
||||
entityType: "",
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -168,6 +262,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
token: validToken,
|
||||
entityType: validEntityType,
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 1000,
|
||||
@@ -183,6 +278,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
token: validToken,
|
||||
entityType: validEntityType,
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -201,6 +297,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
token: validToken,
|
||||
entityType: validEntityType,
|
||||
entityID: validID,
|
||||
domainID: domainID,
|
||||
pageMeta: sdk.PageMetadata{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
@@ -209,7 +306,7 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
EntityID: validID,
|
||||
EntityType: journal.UserEntity,
|
||||
EntityType: journal.GroupEntity,
|
||||
Direction: "desc",
|
||||
},
|
||||
svcRes: journal.JournalsPage{
|
||||
@@ -231,15 +328,20 @@ func TestRetrieveJournal(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, tc.token, tc.svcReq).Return(tc.svcRes, tc.svcErr)
|
||||
resp, err := mgsdk.Journal(tc.entityType, tc.entityID, tc.pageMeta, tc.token)
|
||||
if tc.token == validToken {
|
||||
tc.session = mgauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, mock.Anything).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("RetrieveAll", mock.Anything, tc.session, tc.svcReq).Return(tc.svcRes, tc.svcErr)
|
||||
resp, err := mgsdk.Journal(tc.entityType, tc.entityID, tc.domainID, tc.pageMeta, tc.token)
|
||||
assert.Equal(t, tc.err, err)
|
||||
assert.Equal(t, tc.response, resp)
|
||||
if tc.err == nil {
|
||||
ok := svcCall.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, tc.token, tc.svcReq)
|
||||
ok := svcCall.Parent.AssertCalled(t, "RetrieveAll", mock.Anything, tc.session, tc.svcReq)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1210,9 +1210,9 @@ type SDK interface {
|
||||
// Journal returns a list of journal logs.
|
||||
//
|
||||
// For example:
|
||||
// journals, _ := sdk.Journal("thing", "thingID", PageMetadata{Offset: 0, Limit: 10, Operation: "users.create"}, "token")
|
||||
// journals, _ := sdk.Journal("thing", "thingID","domainID", PageMetadata{Offset: 0, Limit: 10, Operation: "thing.create"}, "token")
|
||||
// fmt.Println(journals)
|
||||
Journal(entityType, entityID string, pm PageMetadata, token string) (journal JournalsPage, err error)
|
||||
Journal(entityType, entityID, domainID string, pm PageMetadata, token string) (journal JournalsPage, err error)
|
||||
}
|
||||
|
||||
type mgSDK struct {
|
||||
|
||||
@@ -1402,9 +1402,9 @@ func (_m *SDK) IssueCert(thingID string, validity string, domainID string, token
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Journal provides a mock function with given fields: entityType, entityID, pm, token
|
||||
func (_m *SDK) Journal(entityType string, entityID string, pm sdk.PageMetadata, token string) (sdk.JournalsPage, error) {
|
||||
ret := _m.Called(entityType, entityID, pm, token)
|
||||
// Journal provides a mock function with given fields: entityType, entityID, domainID, pm, token
|
||||
func (_m *SDK) Journal(entityType string, entityID string, domainID string, pm sdk.PageMetadata, token string) (sdk.JournalsPage, error) {
|
||||
ret := _m.Called(entityType, entityID, domainID, pm, token)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Journal")
|
||||
@@ -1412,17 +1412,17 @@ func (_m *SDK) Journal(entityType string, entityID string, pm sdk.PageMetadata,
|
||||
|
||||
var r0 sdk.JournalsPage
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) (sdk.JournalsPage, error)); ok {
|
||||
return rf(entityType, entityID, pm, token)
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, sdk.PageMetadata, string) (sdk.JournalsPage, error)); ok {
|
||||
return rf(entityType, entityID, domainID, pm, token)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, sdk.PageMetadata, string) sdk.JournalsPage); ok {
|
||||
r0 = rf(entityType, entityID, pm, token)
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, sdk.PageMetadata, string) sdk.JournalsPage); ok {
|
||||
r0 = rf(entityType, entityID, domainID, pm, token)
|
||||
} else {
|
||||
r0 = ret.Get(0).(sdk.JournalsPage)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, sdk.PageMetadata, string) error); ok {
|
||||
r1 = rf(entityType, entityID, pm, token)
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, sdk.PageMetadata, string) error); ok {
|
||||
r1 = rf(entityType, entityID, domainID, pm, token)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
clientPrefix = "client."
|
||||
clientPrefix = "thing."
|
||||
clientCreate = clientPrefix + "create"
|
||||
clientUpdate = clientPrefix + "update"
|
||||
clientChangeStatus = clientPrefix + "change_status"
|
||||
|
||||
Reference in New Issue
Block a user