From 3a02788e3a20da5be9a1a2973b56c2420aebd957 Mon Sep 17 00:00:00 2001 From: Felix Gateru Date: Sun, 10 Nov 2024 20:59:46 +0300 Subject: [PATCH] MG-2507 - Update auth in readers service (#2514) Signed-off-by: Felix Gateru --- api/openapi/readers.yml | 11 +- cli/message.go | 8 +- cli/message_test.go | 5 +- cmd/postgres-reader/main.go | 18 ++- cmd/timescale-reader/main.go | 18 ++- pkg/sdk/go/health_test.go | 16 ++- pkg/sdk/go/message.go | 4 +- pkg/sdk/go/message_test.go | 33 ++++-- pkg/sdk/go/sdk.go | 4 +- pkg/sdk/mocks/sdk.go | 18 +-- readers/api/endpoint.go | 5 +- readers/api/endpoint_test.go | 206 +++++++++++++++++++---------------- readers/api/requests.go | 4 + readers/api/transport.go | 44 ++++---- 14 files changed, 238 insertions(+), 156 deletions(-) diff --git a/api/openapi/readers.yml b/api/openapi/readers.yml index 3ffdf7f8b1..8cf7ea5210 100644 --- a/api/openapi/readers.yml +++ b/api/openapi/readers.yml @@ -35,7 +35,7 @@ tags: url: https://docs.magistrala.abstractmachines.fr/ paths: - /channels/{chanId}/messages: + /{domainID}/channels/{chanId}/messages: get: operationId: getMessages summary: Retrieves messages sent to single channel @@ -47,6 +47,7 @@ paths: tags: - readers parameters: + - $ref: "#/components/parameters/DomainID" - $ref: "#/components/parameters/ChanId" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Offset" @@ -141,6 +142,14 @@ components: description: Time of updating measurement. parameters: + DomainID: + name: domainID + description: Unique domain identifier. + in: path + schema: + type: string + format: uuid + required: true ChanId: name: chanId description: Unique channel identifier. diff --git a/cli/message.go b/cli/message.go index 6b5815bd51..e4cfc0b27f 100644 --- a/cli/message.go +++ b/cli/message.go @@ -28,13 +28,13 @@ var cmdMessages = []cobra.Command{ }, }, { - Use: "read ", + Use: "read ", Short: "Read messages", Long: "Reads all channel messages\n" + "Usage:\n" + - "\tmagistrala-cli messages read --offset --limit - lists all messages with provided offset and limit\n", + "\tmagistrala-cli messages read --offset --limit - lists all messages with provided offset and limit\n", Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { + if len(args) != 3 { logUsageCmd(*cmd, cmd.Use) return } @@ -45,7 +45,7 @@ var cmdMessages = []cobra.Command{ }, } - m, err := sdk.ReadMessages(pageMetadata, args[0], args[1]) + m, err := sdk.ReadMessages(pageMetadata, args[0], args[1], args[2]) if err != nil { logErrorCmd(*cmd, err) return diff --git a/cli/message_test.go b/cli/message_test.go index f98c5e1e31..a145fe6021 100644 --- a/cli/message_test.go +++ b/cli/message_test.go @@ -104,6 +104,7 @@ func TestReadMesageCmd(t *testing.T) { desc: "read message successfully", args: []string{ channel.ID, + domainID, validToken, }, page: mgsdk.MessagesPage{ @@ -124,6 +125,7 @@ func TestReadMesageCmd(t *testing.T) { desc: "read message with invalid args", args: []string{ channel.ID, + domainID, validToken, extraArg, }, @@ -133,6 +135,7 @@ func TestReadMesageCmd(t *testing.T) { desc: "read message with invalid token", args: []string{ channel.ID, + domainID, invalidToken, }, sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized), @@ -143,7 +146,7 @@ func TestReadMesageCmd(t *testing.T) { for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - sdkCall := sdkMock.On("ReadMessages", mock.Anything, tc.args[0], tc.args[1]).Return(tc.page, tc.sdkErr) + sdkCall := sdkMock.On("ReadMessages", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr) out := executeCommand(t, rootCmd, append([]string{readCmd}, tc.args...)...) switch tc.logType { diff --git a/cmd/postgres-reader/main.go b/cmd/postgres-reader/main.go index d07f40a2af..5354061ba7 100644 --- a/cmd/postgres-reader/main.go +++ b/cmd/postgres-reader/main.go @@ -14,6 +14,7 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" mglog "github.com/absmach/magistrala/logger" + authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/authz/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" pgclient "github.com/absmach/magistrala/pkg/postgres" @@ -84,14 +85,14 @@ func main() { } defer db.Close() - authzCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&authzCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + clientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientCfg, env.Options{Prefix: envPrefixAuth}); err != nil { logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) exitCode = 1 return } - authz, authzHandler, err := authsvc.NewAuthorization(ctx, authzCfg) + authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -100,6 +101,15 @@ func main() { defer authzHandler.Close() logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) + authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure()) + thingsClientCfg := grpcclient.Config{} if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) @@ -125,7 +135,7 @@ func main() { exitCode = 1 return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authz, thingsClient, svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, authz, thingsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/cmd/timescale-reader/main.go b/cmd/timescale-reader/main.go index ba6dde416a..2d7a5e05cd 100644 --- a/cmd/timescale-reader/main.go +++ b/cmd/timescale-reader/main.go @@ -14,6 +14,7 @@ import ( chclient "github.com/absmach/callhome/pkg/client" "github.com/absmach/magistrala" mglog "github.com/absmach/magistrala/logger" + authsvcAuthn "github.com/absmach/magistrala/pkg/authn/authsvc" "github.com/absmach/magistrala/pkg/authz/authsvc" "github.com/absmach/magistrala/pkg/grpcclient" pgclient "github.com/absmach/magistrala/pkg/postgres" @@ -84,14 +85,14 @@ func main() { repo := newService(db, logger) - authzCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&authzCfg, env.Options{Prefix: envPrefixAuth}); err != nil { + clientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientCfg, env.Options{Prefix: envPrefixAuth}); err != nil { logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err)) exitCode = 1 return } - authz, authzHandler, err := authsvc.NewAuthorization(ctx, authzCfg) + authz, authzHandler, err := authsvc.NewAuthorization(ctx, clientCfg) if err != nil { logger.Error(err.Error()) exitCode = 1 @@ -100,6 +101,15 @@ func main() { defer authzHandler.Close() logger.Info("Authz successfully connected to auth gRPC server " + authzHandler.Secure()) + authn, authnHandler, err := authsvcAuthn.NewAuthentication(ctx, clientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer authnHandler.Close() + logger.Info("Authn successfully connected to auth gRPC server " + authnHandler.Secure()) + thingsClientCfg := grpcclient.Config{} if err := env.ParseWithOptions(&thingsClientCfg, env.Options{Prefix: envPrefixThings}); err != nil { logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) @@ -123,7 +133,7 @@ func main() { exitCode = 1 return } - hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authz, thingsClient, svcName, cfg.InstanceID), logger) + hs := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(repo, authn, authz, thingsClient, svcName, cfg.InstanceID), logger) if cfg.SendTelemetry { chc := chclient.New(svcName, magistrala.Version, logger, cancel) diff --git a/pkg/sdk/go/health_test.go b/pkg/sdk/go/health_test.go index e6b40f73c7..f30cf045df 100644 --- a/pkg/sdk/go/health_test.go +++ b/pkg/sdk/go/health_test.go @@ -13,8 +13,12 @@ import ( bmocks "github.com/absmach/magistrala/bootstrap/mocks" mglog "github.com/absmach/magistrala/logger" authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" + authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" "github.com/absmach/magistrala/pkg/errors" sdk "github.com/absmach/magistrala/pkg/sdk/go" + readersapi "github.com/absmach/magistrala/readers/api" + readersmocks "github.com/absmach/magistrala/readers/mocks" + thmocks "github.com/absmach/magistrala/things/mocks" "github.com/stretchr/testify/assert" ) @@ -31,7 +35,7 @@ func TestHealth(t *testing.T) { bootstrapTs := setupMinimalBootstrap() defer bootstrapTs.Close() - readerTs, _, _ := setupReader() + readerTs := setupMinimalReader() defer readerTs.Close() httpAdapterTs, _, _ := setupMessages() @@ -128,3 +132,13 @@ func setupMinimalBootstrap() *httptest.Server { return httptest.NewServer(mux) } + +func setupMinimalReader() *httptest.Server { + repo := new(readersmocks.MessageRepository) + authz := new(authzmocks.Authorization) + authn := new(authnmocks.Authentication) + things := new(thmocks.ThingsServiceClient) + + mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") + return httptest.NewServer(mux) +} diff --git a/pkg/sdk/go/message.go b/pkg/sdk/go/message.go index 2dfdbd87dd..0ff16e8d1e 100644 --- a/pkg/sdk/go/message.go +++ b/pkg/sdk/go/message.go @@ -32,7 +32,7 @@ func (sdk mgSDK) SendMessage(chanName, msg, key string) errors.SDKError { return err } -func (sdk mgSDK) ReadMessages(pm MessagePageMetadata, chanName, token string) (MessagesPage, errors.SDKError) { +func (sdk mgSDK) ReadMessages(pm MessagePageMetadata, chanName, domainID, token string) (MessagesPage, errors.SDKError) { chanNameParts := strings.SplitN(chanName, ".", channelParts) chanID := chanNameParts[0] subtopicPart := "" @@ -40,7 +40,7 @@ func (sdk mgSDK) ReadMessages(pm MessagePageMetadata, chanName, token string) (M subtopicPart = fmt.Sprintf("?subtopic=%s", chanNameParts[1]) } - readMessagesEndpoint := fmt.Sprintf("channels/%s/messages%s", chanID, subtopicPart) + readMessagesEndpoint := fmt.Sprintf("%s/channels/%s/messages%s", domainID, chanID, subtopicPart) msgURL, err := sdk.withMessageQueryParams(sdk.readerURL, readMessagesEndpoint, pm) if err != nil { return MessagesPage{}, errors.NewSDKError(err) diff --git a/pkg/sdk/go/message_test.go b/pkg/sdk/go/message_test.go index 49ad8fcd12..3f5ad3df6b 100644 --- a/pkg/sdk/go/message_test.go +++ b/pkg/sdk/go/message_test.go @@ -14,6 +14,8 @@ import ( "github.com/absmach/magistrala/http/api" 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" authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -50,13 +52,14 @@ func setupMessages() (*httptest.Server, *thmocks.ThingsServiceClient, *pubsub.Pu return httptest.NewServer(http.HandlerFunc(mp.ServeHTTP)), things, pub } -func setupReader() (*httptest.Server, *authzmocks.Authorization, *readersmocks.MessageRepository) { +func setupReader() (*httptest.Server, *authzmocks.Authorization, *authnmocks.Authentication, *readersmocks.MessageRepository) { repo := new(readersmocks.MessageRepository) authz := new(authzmocks.Authorization) + authn := new(authnmocks.Authentication) things := new(thmocks.ThingsServiceClient) - mux := readersapi.MakeHandler(repo, authz, things, "test", "") - return httptest.NewServer(mux), authz, repo + mux := readersapi.MakeHandler(repo, authn, authz, things, "test", "") + return httptest.NewServer(mux), authz, authn, repo } func TestSendMessage(t *testing.T) { @@ -196,7 +199,7 @@ func TestSetContentType(t *testing.T) { } func TestReadMessages(t *testing.T) { - ts, authz, repo := setupReader() + ts, authz, authn, repo := setupReader() defer ts.Close() channelID := "channelID" @@ -220,8 +223,10 @@ func TestReadMessages(t *testing.T) { desc string token string chanName string + domainID string messagePageMeta sdk.MessagePageMetadata - authErr error + authzErr error + authnErr error repoRes readers.MessagesPage repoErr error response sdk.MessagesPage @@ -231,6 +236,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages successfully", token: validToken, chanName: channelID, + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -257,6 +263,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages successfully with subtopic", token: validToken, chanName: channelID + ".subtopic", + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -281,6 +288,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages with invalid token", token: invalidToken, chanName: channelID, + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -289,7 +297,7 @@ func TestReadMessages(t *testing.T) { Subtopic: "subtopic", Publisher: validID, }, - authErr: svcerr.ErrAuthorization, + authzErr: svcerr.ErrAuthorization, repoRes: readers.MessagesPage{}, response: sdk.MessagesPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(svcerr.ErrAuthorization, svcerr.ErrAuthorization), http.StatusUnauthorized), @@ -298,6 +306,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages with empty token", token: "", chanName: channelID, + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -306,7 +315,7 @@ func TestReadMessages(t *testing.T) { Subtopic: "subtopic", Publisher: validID, }, - authErr: svcerr.ErrAuthorization, + authnErr: svcerr.ErrAuthentication, repoRes: readers.MessagesPage{}, response: sdk.MessagesPage{}, err: errors.NewSDKErrorWithStatus(errors.Wrap(apiutil.ErrValidation, apiutil.ErrBearerToken), http.StatusUnauthorized), @@ -315,6 +324,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages with empty channel ID", token: validToken, chanName: "", + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -332,6 +342,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages with invalid message page metadata", token: validToken, chanName: channelID, + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -352,6 +363,7 @@ func TestReadMessages(t *testing.T) { desc: "read messages with response that cannot be unmarshalled", token: validToken, chanName: channelID, + domainID: validID, messagePageMeta: sdk.MessagePageMetadata{ PageMetadata: sdk.PageMetadata{ Offset: 0, @@ -371,9 +383,11 @@ func TestReadMessages(t *testing.T) { } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.authErr) + authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.authzErr) + authCall1 := authn.On("Authenticate", mock.Anything, tc.token).Return(mgauthn.Session{UserID: validID}, tc.authnErr) repoCall := repo.On("ReadAll", channelID, mock.Anything).Return(tc.repoRes, tc.repoErr) - response, err := mgsdk.ReadMessages(tc.messagePageMeta, tc.chanName, tc.token) + response, err := mgsdk.ReadMessages(tc.messagePageMeta, tc.chanName, tc.domainID, tc.token) + fmt.Println(err) assert.Equal(t, tc.err, err) assert.Equal(t, tc.response, response) if tc.err == nil { @@ -381,6 +395,7 @@ func TestReadMessages(t *testing.T) { assert.True(t, ok) } authCall.Unset() + authCall1.Unset() repoCall.Unset() }) } diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go index 6b65939768..8cb1bf6f64 100644 --- a/pkg/sdk/go/sdk.go +++ b/pkg/sdk/go/sdk.go @@ -884,9 +884,9 @@ type SDK interface { // Offset: 0, // Limit: 10, // } - // msgs, _ := sdk.ReadMessages(pm,"channelID", "token") + // msgs, _ := sdk.ReadMessages(pm,"channelID", "domainID", "token") // fmt.Println(msgs) - ReadMessages(pm MessagePageMetadata, chanID, token string) (MessagesPage, errors.SDKError) + ReadMessages(pm MessagePageMetadata, chanID, domainID, token string) (MessagesPage, errors.SDKError) // SetContentType sets message content type. // diff --git a/pkg/sdk/mocks/sdk.go b/pkg/sdk/mocks/sdk.go index 0b95a2e95f..9ef786d750 100644 --- a/pkg/sdk/mocks/sdk.go +++ b/pkg/sdk/mocks/sdk.go @@ -1820,9 +1820,9 @@ func (_m *SDK) Parents(id string, pm sdk.PageMetadata, domainID string, token st return r0, r1 } -// ReadMessages provides a mock function with given fields: pm, chanID, token -func (_m *SDK) ReadMessages(pm sdk.MessagePageMetadata, chanID string, token string) (sdk.MessagesPage, errors.SDKError) { - ret := _m.Called(pm, chanID, token) +// ReadMessages provides a mock function with given fields: pm, chanID, domainID, token +func (_m *SDK) ReadMessages(pm sdk.MessagePageMetadata, chanID string, domainID string, token string) (sdk.MessagesPage, errors.SDKError) { + ret := _m.Called(pm, chanID, domainID, token) if len(ret) == 0 { panic("no return value specified for ReadMessages") @@ -1830,17 +1830,17 @@ func (_m *SDK) ReadMessages(pm sdk.MessagePageMetadata, chanID string, token str var r0 sdk.MessagesPage var r1 errors.SDKError - if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string) (sdk.MessagesPage, errors.SDKError)); ok { - return rf(pm, chanID, token) + if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string, string) (sdk.MessagesPage, errors.SDKError)); ok { + return rf(pm, chanID, domainID, token) } - if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string) sdk.MessagesPage); ok { - r0 = rf(pm, chanID, token) + if rf, ok := ret.Get(0).(func(sdk.MessagePageMetadata, string, string, string) sdk.MessagesPage); ok { + r0 = rf(pm, chanID, domainID, token) } else { r0 = ret.Get(0).(sdk.MessagesPage) } - if rf, ok := ret.Get(1).(func(sdk.MessagePageMetadata, string, string) errors.SDKError); ok { - r1 = rf(pm, chanID, token) + if rf, ok := ret.Get(1).(func(sdk.MessagePageMetadata, string, string, string) errors.SDKError); ok { + r1 = rf(pm, chanID, domainID, token) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.SDKError) diff --git a/readers/api/endpoint.go b/readers/api/endpoint.go index 699b643ad5..794063f79d 100644 --- a/readers/api/endpoint.go +++ b/readers/api/endpoint.go @@ -8,6 +8,7 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" mgauthz "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" @@ -15,14 +16,14 @@ import ( "github.com/go-kit/kit/endpoint" ) -func listMessagesEndpoint(svc readers.MessageRepository, authz mgauthz.Authorization, thingsClient magistrala.ThingsServiceClient) endpoint.Endpoint { +func listMessagesEndpoint(svc readers.MessageRepository, authn mgauthn.Authentication, authz mgauthz.Authorization, thingsClient magistrala.ThingsServiceClient) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(listMessagesReq) if err := req.validate(); err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } - if err := authorize(ctx, req, authz, thingsClient); err != nil { + if err := authorize(ctx, req, authn, authz, thingsClient); err != nil { return nil, errors.Wrap(svcerr.ErrAuthorization, err) } diff --git a/readers/api/endpoint_test.go b/readers/api/endpoint_test.go index 1d982299be..156e79ec79 100644 --- a/readers/api/endpoint_test.go +++ b/readers/api/endpoint_test.go @@ -14,6 +14,8 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/internal/testsutil" "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" + authnmocks "github.com/absmach/magistrala/pkg/authn/mocks" authzmocks "github.com/absmach/magistrala/pkg/authz/mocks" svcerr "github.com/absmach/magistrala/pkg/errors/service" "github.com/absmach/magistrala/pkg/transformers/senml" @@ -42,15 +44,17 @@ const ( ) var ( - v float64 = 5 - vs = "value" - vb = true - vd = "dataValue" - sum float64 = 42 + v float64 = 5 + vs = "value" + vb = true + vd = "dataValue" + sum float64 = 42 + domainID = testsutil.GenerateUUID(&testing.T{}) + validSession = mgauthn.Session{UserID: testsutil.GenerateUUID(&testing.T{})} ) -func newServer(repo *mocks.MessageRepository, authz *authzmocks.Authorization, thingsAuthzClient *thmocks.ThingsServiceClient) *httptest.Server { - mux := api.MakeHandler(repo, authz, thingsAuthzClient, svcName, instanceID) +func newServer(repo *mocks.MessageRepository, authn *authnmocks.Authentication, authz *authzmocks.Authorization, thingsAuthzClient *thmocks.ThingsServiceClient) *httptest.Server { + mux := api.MakeHandler(repo, authn, authz, thingsAuthzClient, svcName, instanceID) return httptest.NewServer(mux) } @@ -129,8 +133,9 @@ func TestReadAll(t *testing.T) { repo := new(mocks.MessageRepository) authz := new(authzmocks.Authorization) + authn := new(authnmocks.Authentication) things := new(thmocks.ThingsServiceClient) - ts := newServer(repo, authz, things) + ts := newServer(repo, authn, authz, things) defer ts.Close() cases := []struct { @@ -142,11 +147,12 @@ func TestReadAll(t *testing.T) { authResponse bool status int res pageRes + authnErr error err error }{ { desc: "read page with valid offset and limit", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -158,7 +164,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with valid offset and limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -168,73 +174,80 @@ func TestReadAll(t *testing.T) { Messages: messages[0:10], }, }, + { + desc: "read page as user without domain id", + url: fmt.Sprintf("%s/%s/channels/%s/messages", ts.URL, "", chanID), + token: userToken, + status: http.StatusBadRequest, + }, { desc: "read page with negative offset as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=-1&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=-1&limit=10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with negative limit as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=-10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=-10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with zero limit as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=0", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=0", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer offset as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=abc&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=abc&limit=10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer limit as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=abc", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=abc", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid channel id as thing", - url: fmt.Sprintf("%s/channels//messages?offset=0&limit=10", ts.URL), + url: fmt.Sprintf("%s/%s/channels//messages?offset=0&limit=10", ts.URL, ""), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with multiple offset as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with multiple limit as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with empty token as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, "", chanID), token: "", authResponse: false, + authnErr: svcerr.ErrAuthentication, status: http.StatusUnauthorized, - err: svcerr.ErrAuthorization, + err: svcerr.ErrAuthentication, }, { desc: "read page with default offset as thing", - url: fmt.Sprintf("%s/channels/%s/messages?limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?limit=10", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -246,7 +259,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with default limit as thing", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -258,7 +271,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with senml format as thing", - url: fmt.Sprintf("%s/channels/%s/messages?format=messages", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?format=messages", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -270,7 +283,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with subtopic as thing", - url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, "", chanID, subtopic, httpProt), key: thingToken, authResponse: true, status: http.StatusOK, @@ -282,7 +295,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with subtopic and protocol as thing", - url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, "", chanID, subtopic, httpProt), key: thingToken, authResponse: true, status: http.StatusOK, @@ -294,7 +307,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with publisher as thing", - url: fmt.Sprintf("%s/channels/%s/messages?publisher=%s", ts.URL, chanID, pubID2), + url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, "", chanID, pubID2), key: thingToken, authResponse: true, status: http.StatusOK, @@ -304,10 +317,9 @@ func TestReadAll(t *testing.T) { Messages: queryMsgs[0:10], }, }, - { desc: "read page with protocol as thing", - url: fmt.Sprintf("%s/channels/%s/messages?protocol=http", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -319,7 +331,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with name as thing", - url: fmt.Sprintf("%s/channels/%s/messages?name=%s", ts.URL, chanID, msgName), + url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, "", chanID, msgName), key: thingToken, authResponse: true, status: http.StatusOK, @@ -331,7 +343,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f", ts.URL, chanID, v), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, "", chanID, v), key: thingToken, authResponse: true, status: http.StatusOK, @@ -343,7 +355,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and equal comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v, readers.EqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v, readers.EqualKey), key: thingToken, authResponse: true, status: http.StatusOK, @@ -355,7 +367,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v+1, readers.LowerThanKey), key: thingToken, authResponse: true, status: http.StatusOK, @@ -367,7 +379,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than-or-equal comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanEqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v+1, readers.LowerThanEqualKey), key: thingToken, authResponse: true, status: http.StatusOK, @@ -379,7 +391,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v-1, readers.GreaterThanKey), key: thingToken, authResponse: true, status: http.StatusOK, @@ -391,7 +403,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than-or-equal comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanEqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, "", chanID, v-1, readers.GreaterThanEqualKey), key: thingToken, authResponse: true, status: http.StatusOK, @@ -403,21 +415,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=ab01", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with value and wrong comparator as thing", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, chanID, v-1), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, "", chanID, v-1), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with boolean value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?vb=true", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=true", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -429,14 +441,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-boolean value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?vb=yes", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with string value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?vs=%s", ts.URL, chanID, vs), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vs=%s", ts.URL, "", chanID, vs), key: thingToken, authResponse: true, status: http.StatusOK, @@ -448,7 +460,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with data value as thing", - url: fmt.Sprintf("%s/channels/%s/messages?vd=%s", ts.URL, chanID, vd), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, "", chanID, vd), key: thingToken, authResponse: true, status: http.StatusOK, @@ -460,21 +472,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?from=ABCD", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-float to as thing", - url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?to=ABCD", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with from/to as thing", - url: fmt.Sprintf("%s/channels/%s/messages?from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusOK, @@ -486,14 +498,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with aggregation as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with interval as thing", - url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?interval=10h", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusOK, @@ -505,14 +517,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with aggregation and interval as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, "", chanID), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval, to and from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusOK, @@ -524,42 +536,42 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with invalid aggregation and valid interval, to and from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid interval and valid aggregation, to and from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, "", chanID, messages[19].Time, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with missing from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, "", chanID, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid from as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, "", chanID, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid to as thing", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, "", chanID, messages[4].Time), key: thingToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with valid offset and limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -571,49 +583,49 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with negative offset as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=-1&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=-1&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with negative limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=-10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=-10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with zero limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=0", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=0", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer offset as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=abc&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=abc&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-integer limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=abc", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=abc", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid channel id as user", - url: fmt.Sprintf("%s/channels//messages?offset=0&limit=10", ts.URL), + url: fmt.Sprintf("%s/%s/channels//messages?offset=0&limit=10", ts.URL, domainID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid token as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: invalidToken, authResponse: false, status: http.StatusUnauthorized, @@ -621,21 +633,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with multiple offset as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&offset=1&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with multiple limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=20&limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with empty token as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0&limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0&limit=10", ts.URL, domainID, chanID), token: "", authResponse: false, status: http.StatusUnauthorized, @@ -643,7 +655,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with default offset as user", - url: fmt.Sprintf("%s/channels/%s/messages?limit=10", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?limit=10", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -655,7 +667,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with default limit as user", - url: fmt.Sprintf("%s/channels/%s/messages?offset=0", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?offset=0", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -667,7 +679,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with senml format as user", - url: fmt.Sprintf("%s/channels/%s/messages?format=messages", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?format=messages", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -679,7 +691,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with subtopic as user", - url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, domainID, chanID, subtopic, httpProt), token: userToken, authResponse: true, status: http.StatusOK, @@ -691,7 +703,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with subtopic and protocol as user", - url: fmt.Sprintf("%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, chanID, subtopic, httpProt), + url: fmt.Sprintf("%s/%s/channels/%s/messages?subtopic=%s&protocol=%s", ts.URL, domainID, chanID, subtopic, httpProt), token: userToken, authResponse: true, status: http.StatusOK, @@ -703,7 +715,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with publisher as user", - url: fmt.Sprintf("%s/channels/%s/messages?publisher=%s", ts.URL, chanID, pubID2), + url: fmt.Sprintf("%s/%s/channels/%s/messages?publisher=%s", ts.URL, domainID, chanID, pubID2), token: userToken, authResponse: true, status: http.StatusOK, @@ -715,7 +727,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with protocol as user", - url: fmt.Sprintf("%s/channels/%s/messages?protocol=http", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?protocol=http", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -727,7 +739,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with name as user", - url: fmt.Sprintf("%s/channels/%s/messages?name=%s", ts.URL, chanID, msgName), + url: fmt.Sprintf("%s/%s/channels/%s/messages?name=%s", ts.URL, domainID, chanID, msgName), token: userToken, authResponse: true, status: http.StatusOK, @@ -739,7 +751,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f", ts.URL, chanID, v), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f", ts.URL, domainID, chanID, v), token: userToken, authResponse: true, status: http.StatusOK, @@ -751,7 +763,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and equal comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v, readers.EqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v, readers.EqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -763,7 +775,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v+1, readers.LowerThanKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -775,7 +787,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and lower-than-or-equal comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v+1, readers.LowerThanEqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v+1, readers.LowerThanEqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -787,7 +799,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v-1, readers.GreaterThanKey), token: userToken, status: http.StatusOK, authResponse: true, @@ -799,7 +811,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with value and greater-than-or-equal comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, chanID, v-1, readers.GreaterThanEqualKey), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=%s", ts.URL, domainID, chanID, v-1, readers.GreaterThanEqualKey), token: userToken, authResponse: true, status: http.StatusOK, @@ -811,21 +823,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float value as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=ab01", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=ab01", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with value and wrong comparator as user", - url: fmt.Sprintf("%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, chanID, v-1), + url: fmt.Sprintf("%s/%s/channels/%s/messages?v=%f&comparator=wrong", ts.URL, domainID, chanID, v-1), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with boolean value as user", - url: fmt.Sprintf("%s/channels/%s/messages?vb=true", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=true", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusOK, @@ -837,14 +849,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-boolean value as user", - url: fmt.Sprintf("%s/channels/%s/messages?vb=yes", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vb=yes", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with string value as user", - url: fmt.Sprintf("%s/channels/%s/messages?vs=%s", ts.URL, chanID, vs), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vs=%s", ts.URL, domainID, chanID, vs), token: userToken, authResponse: true, status: http.StatusOK, @@ -856,7 +868,7 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with data value as user", - url: fmt.Sprintf("%s/channels/%s/messages?vd=%s", ts.URL, chanID, vd), + url: fmt.Sprintf("%s/%s/channels/%s/messages?vd=%s", ts.URL, domainID, chanID, vd), token: userToken, authResponse: true, status: http.StatusOK, @@ -868,21 +880,21 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with non-float from as user", - url: fmt.Sprintf("%s/channels/%s/messages?from=ABCD", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?from=ABCD", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with non-float to as user", - url: fmt.Sprintf("%s/channels/%s/messages?to=ABCD", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?to=ABCD", ts.URL, domainID, chanID), token: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with from/to as user", - url: fmt.Sprintf("%s/channels/%s/messages?from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), token: userToken, authResponse: true, status: http.StatusOK, @@ -894,14 +906,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with aggregation as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX", ts.URL, domainID, chanID), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with interval as user", - url: fmt.Sprintf("%s/channels/%s/messages?interval=10h", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?interval=10h", ts.URL, domainID, chanID), key: userToken, authResponse: true, status: http.StatusOK, @@ -913,14 +925,14 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with aggregation and interval as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, chanID), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h", ts.URL, domainID, chanID), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval, to and from as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), key: userToken, authResponse: true, status: http.StatusOK, @@ -932,35 +944,35 @@ func TestReadAll(t *testing.T) { }, { desc: "read page with invalid aggregation and valid interval, to and from as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=invalid&interval=10h&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with invalid interval and valid aggregation, to and from as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, chanID, messages[19].Time, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10hrs&from=%f&to=%f", ts.URL, domainID, chanID, messages[19].Time, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with missing from as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=%f", ts.URL, domainID, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid from as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&to=ABCD&from=%f", ts.URL, domainID, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, }, { desc: "read page with aggregation, interval and to with invalid to as user", - url: fmt.Sprintf("%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, chanID, messages[4].Time), + url: fmt.Sprintf("%s/%s/channels/%s/messages?aggregation=MAX&interval=10h&from=%f&to=ABCD", ts.URL, domainID, chanID, messages[4].Time), key: userToken, authResponse: true, status: http.StatusBadRequest, @@ -969,6 +981,7 @@ func TestReadAll(t *testing.T) { for _, tc := range cases { authCall := authz.On("Authorize", mock.Anything, mock.Anything).Return(tc.err) + authCall1 := authn.On("Authenticate", mock.Anything, tc.token).Return(validSession, tc.authnErr) repo.On("ReadAll", chanID, tc.res.PageMetadata).Return(readers.MessagesPage{Total: tc.res.Total, Messages: fromSenml(tc.res.Messages)}, nil) if tc.key != "" { authCall = things.On("Authorize", mock.Anything, mock.Anything).Return(&magistrala.ThingsAuthzRes{Authorized: tc.authResponse}, tc.err) @@ -992,6 +1005,7 @@ func TestReadAll(t *testing.T) { assert.Equal(t, tc.res.Total, page.Total, fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.res.Total, page.Total)) assert.ElementsMatch(t, tc.res.Messages, page.Messages, fmt.Sprintf("%s: got incorrect body from response", tc.desc)) authCall.Unset() + authCall1.Unset() } } diff --git a/readers/api/requests.go b/readers/api/requests.go index c32be45b96..df08f79639 100644 --- a/readers/api/requests.go +++ b/readers/api/requests.go @@ -20,6 +20,7 @@ type listMessagesReq struct { chanID string token string key string + domainID string pageMeta readers.PageMetadata } @@ -27,6 +28,9 @@ func (req listMessagesReq) validate() error { if req.token == "" && req.key == "" { return apiutil.ErrBearerToken } + if req.token != "" && req.domainID == "" { + return apiutil.ErrMissingDomainID + } if req.chanID == "" { return apiutil.ErrMissingID diff --git a/readers/api/transport.go b/readers/api/transport.go index 82a5d17fd5..e2715529c1 100644 --- a/readers/api/transport.go +++ b/readers/api/transport.go @@ -10,9 +10,11 @@ import ( "github.com/absmach/magistrala" "github.com/absmach/magistrala/pkg/apiutil" + mgauthn "github.com/absmach/magistrala/pkg/authn" mgauthz "github.com/absmach/magistrala/pkg/authz" "github.com/absmach/magistrala/pkg/errors" svcerr "github.com/absmach/magistrala/pkg/errors/service" + "github.com/absmach/magistrala/pkg/policies" "github.com/absmach/magistrala/readers" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" @@ -43,26 +45,19 @@ const ( defLimit = 10 defOffset = 0 defFormat = "messages" - - tokenKind = "token" - thingType = "thing" - userType = "user" - subscribePermission = "subscribe" - viewPermission = "view" - groupType = "group" ) var errUserAccess = errors.New("user has no permission") // MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(svc readers.MessageRepository, authz mgauthz.Authorization, things magistrala.ThingsServiceClient, svcName, instanceID string) http.Handler { +func MakeHandler(svc readers.MessageRepository, authn mgauthn.Authentication, authz mgauthz.Authorization, things magistrala.ThingsServiceClient, svcName, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(encodeError), } mux := chi.NewRouter() - mux.Get("/channels/{chanID}/messages", kithttp.NewServer( - listMessagesEndpoint(svc, authz, things), + mux.Get("/{domainID}/channels/{chanID}/messages", kithttp.NewServer( + listMessagesEndpoint(svc, authn, authz, things), decodeList, encodeResponse, opts..., @@ -159,9 +154,10 @@ func decodeList(_ context.Context, r *http.Request) (interface{}, error) { } req := listMessagesReq{ - chanID: chi.URLParam(r, "chanID"), - token: apiutil.ExtractBearerToken(r), - key: apiutil.ExtractThingKey(r), + chanID: chi.URLParam(r, "chanID"), + token: apiutil.ExtractBearerToken(r), + key: apiutil.ExtractThingKey(r), + domainID: chi.URLParam(r, "domainID"), pageMeta: readers.PageMetadata{ Offset: offset, Limit: limit, @@ -219,7 +215,8 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrInvalidAggregation), errors.Contains(err, apiutil.ErrInvalidInterval), errors.Contains(err, apiutil.ErrMissingFrom), - errors.Contains(err, apiutil.ErrMissingTo): + errors.Contains(err, apiutil.ErrMissingTo), + errors.Contains(err, apiutil.ErrMissingDomainID): w.WriteHeader(http.StatusBadRequest) case errors.Contains(err, svcerr.ErrAuthentication), errors.Contains(err, svcerr.ErrAuthorization), @@ -242,15 +239,20 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) { } } -func authorize(ctx context.Context, req listMessagesReq, authz mgauthz.Authorization, things magistrala.ThingsServiceClient) (err error) { +func authorize(ctx context.Context, req listMessagesReq, authn mgauthn.Authentication, authz mgauthz.Authorization, things magistrala.ThingsServiceClient) (err error) { switch { case req.token != "": + session, err := authn.Authenticate(ctx, req.token) + if err != nil { + return errors.Wrap(svcerr.ErrAuthentication, err) + } if err = authz.Authorize(ctx, mgauthz.PolicyReq{ - SubjectType: userType, - SubjectKind: tokenKind, - Subject: req.token, - Permission: viewPermission, - ObjectType: groupType, + Domain: req.domainID, + SubjectType: policies.UserType, + SubjectKind: policies.UsersKind, + Subject: req.domainID + "_" + session.UserID, + Permission: policies.ViewPermission, + ObjectType: policies.GroupType, Object: req.chanID, }); err != nil { e, ok := status.FromError(err) @@ -264,7 +266,7 @@ func authorize(ctx context.Context, req listMessagesReq, authz mgauthz.Authoriza if _, err = things.Authorize(ctx, &magistrala.ThingsAuthzReq{ ThingKey: req.key, ChannelID: req.chanID, - Permission: subscribePermission, + Permission: policies.SubscribePermission, }); err != nil { e, ok := status.FromError(err) if ok && e.Code() == codes.PermissionDenied {